initial commit
This commit is contained in:
parent
4ca641228c
commit
6bd1e95657
|
@ -1,10 +1,9 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../lib/constants';
|
||||
import { TSupportedThemes, withTheme } from '../../../theme';
|
||||
import { useTheme } from '../../../theme';
|
||||
import * as List from '../../../containers/List';
|
||||
import DropdownItemFilter from './DropdownItemFilter';
|
||||
import DropdownItemHeader from './DropdownItemHeader';
|
||||
|
@ -14,33 +13,27 @@ import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment';
|
|||
const ANIMATION_DURATION = 200;
|
||||
|
||||
interface IDropdownProps {
|
||||
theme?: TSupportedThemes;
|
||||
currentDepartment: ILivechatDepartment;
|
||||
onClose: () => void;
|
||||
onDepartmentSelected: (value: ILivechatDepartment) => void;
|
||||
departments: ILivechatDepartment[];
|
||||
}
|
||||
|
||||
class Dropdown extends React.Component<IDropdownProps> {
|
||||
private animatedValue: Animated.Value;
|
||||
const Dropdown = ({ currentDepartment, onClose, onDepartmentSelected, departments }: IDropdownProps) => {
|
||||
const animatedValue = useRef(new Animated.Value(0)).current;
|
||||
const { theme } = useTheme();
|
||||
|
||||
constructor(props: IDropdownProps) {
|
||||
super(props);
|
||||
this.animatedValue = new Animated.Value(0);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Animated.timing(this.animatedValue, {
|
||||
useEffect(() => {
|
||||
Animated.timing(animatedValue, {
|
||||
toValue: 1,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
useNativeDriver: true
|
||||
}).start();
|
||||
}
|
||||
}, [animatedValue]);
|
||||
|
||||
close = () => {
|
||||
const { onClose } = this.props;
|
||||
Animated.timing(this.animatedValue, {
|
||||
const close = () => {
|
||||
Animated.timing(animatedValue, {
|
||||
toValue: 0,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
|
@ -48,58 +41,57 @@ class Dropdown extends React.Component<IDropdownProps> {
|
|||
}).start(() => onClose());
|
||||
};
|
||||
|
||||
render() {
|
||||
const { theme, currentDepartment, onDepartmentSelected, departments } = this.props;
|
||||
const heightDestination = 0;
|
||||
const translateY = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-300, heightDestination] // approximated height of the component when closed/open
|
||||
});
|
||||
const backdropOpacity = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, themes[theme!].backdropOpacity]
|
||||
});
|
||||
const heightDestination = 0;
|
||||
const maxRows = 5;
|
||||
|
||||
const maxRows = 5;
|
||||
return (
|
||||
<>
|
||||
<TouchableWithoutFeedback onPress={this.close}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
backgroundColor: themes[theme!].backdropColor,
|
||||
opacity: backdropOpacity,
|
||||
top: heightDestination
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
const translateY = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-300, heightDestination] // approximated height of the component when closed/open
|
||||
});
|
||||
|
||||
const backdropOpacity = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, themes[theme!].backdropOpacity]
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableWithoutFeedback onPress={close}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.dropdownContainer,
|
||||
styles.backdrop,
|
||||
{
|
||||
transform: [{ translateY }],
|
||||
backgroundColor: themes[theme!].backgroundColor,
|
||||
borderColor: themes[theme!].separatorColor
|
||||
backgroundColor: themes[theme!].backdropColor,
|
||||
opacity: backdropOpacity,
|
||||
top: heightDestination
|
||||
}
|
||||
]}
|
||||
>
|
||||
<DropdownItemHeader department={currentDepartment} onPress={this.close} />
|
||||
<List.Separator />
|
||||
<FlatList
|
||||
style={{ maxHeight: maxRows * ROW_HEIGHT }}
|
||||
data={departments}
|
||||
keyExtractor={item => item._id}
|
||||
renderItem={({ item }) => (
|
||||
<DropdownItemFilter onPress={onDepartmentSelected} currentDepartment={currentDepartment} value={item} />
|
||||
)}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.dropdownContainer,
|
||||
{
|
||||
transform: [{ translateY }],
|
||||
backgroundColor: themes[theme!].backgroundColor,
|
||||
borderColor: themes[theme!].separatorColor
|
||||
}
|
||||
]}
|
||||
>
|
||||
<DropdownItemHeader department={currentDepartment} onPress={close} />
|
||||
<List.Separator />
|
||||
<FlatList
|
||||
style={{ maxHeight: maxRows * ROW_HEIGHT }}
|
||||
data={departments}
|
||||
keyExtractor={item => item._id}
|
||||
renderItem={({ item }) => (
|
||||
<DropdownItemFilter onPress={onDepartmentSelected} currentDepartment={currentDepartment} value={item} />
|
||||
)}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withTheme(withSafeAreaInsets(Dropdown));
|
||||
export default Dropdown;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import React, { memo, useEffect, useRef } from 'react';
|
||||
import { Animated, Easing, Switch, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||
|
||||
import Touch from '../../containers/Touch';
|
||||
|
@ -26,73 +26,75 @@ interface IDirectoryOptionsProps {
|
|||
theme: TSupportedThemes;
|
||||
}
|
||||
|
||||
export default class DirectoryOptions extends PureComponent<IDirectoryOptionsProps, any> {
|
||||
private animatedValue: Animated.Value;
|
||||
const DirectoryOptions = memo(
|
||||
({
|
||||
type: propType,
|
||||
globalUsers,
|
||||
isFederationEnabled,
|
||||
close: onClose,
|
||||
changeType,
|
||||
toggleWorkspace,
|
||||
theme
|
||||
}: IDirectoryOptionsProps) => {
|
||||
const animatedValue = useRef(new Animated.Value(0)).current;
|
||||
|
||||
constructor(props: IDirectoryOptionsProps) {
|
||||
super(props);
|
||||
this.animatedValue = new Animated.Value(0);
|
||||
}
|
||||
useEffect(() => {
|
||||
Animated.timing(animatedValue, {
|
||||
toValue: 1,
|
||||
...ANIMATION_PROPS
|
||||
}).start();
|
||||
}, [animatedValue]);
|
||||
|
||||
componentDidMount() {
|
||||
Animated.timing(this.animatedValue, {
|
||||
toValue: 1,
|
||||
...ANIMATION_PROPS
|
||||
}).start();
|
||||
}
|
||||
const close = () => {
|
||||
Animated.timing(animatedValue, {
|
||||
toValue: 0,
|
||||
...ANIMATION_PROPS
|
||||
}).start(() => onClose());
|
||||
};
|
||||
|
||||
close = () => {
|
||||
const { close } = this.props;
|
||||
Animated.timing(this.animatedValue, {
|
||||
toValue: 0,
|
||||
...ANIMATION_PROPS
|
||||
}).start(() => close());
|
||||
};
|
||||
const renderItem = (itemType: string) => {
|
||||
let text = 'Users';
|
||||
let icon: TIconsName = 'user';
|
||||
if (itemType === 'channels') {
|
||||
text = 'Channels';
|
||||
icon = 'channel-public';
|
||||
}
|
||||
|
||||
renderItem = (itemType: string) => {
|
||||
const { changeType, type: propType, theme } = this.props;
|
||||
let text = 'Users';
|
||||
let icon: TIconsName = 'user';
|
||||
if (itemType === 'channels') {
|
||||
text = 'Channels';
|
||||
icon = 'channel-public';
|
||||
}
|
||||
if (itemType === 'teams') {
|
||||
text = 'Teams';
|
||||
icon = 'teams';
|
||||
}
|
||||
|
||||
if (itemType === 'teams') {
|
||||
text = 'Teams';
|
||||
icon = 'teams';
|
||||
}
|
||||
return (
|
||||
<Touch onPress={() => changeType(itemType)} style={styles.dropdownItemButton} accessibilityLabel={I18n.t(text)}>
|
||||
<View style={styles.dropdownItemContainer}>
|
||||
<CustomIcon name={icon} size={22} color={themes[theme].bodyText} style={styles.dropdownItemIcon} />
|
||||
<Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text>
|
||||
{propType === itemType ? <Check /> : null}
|
||||
</View>
|
||||
</Touch>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Touch onPress={() => changeType(itemType)} style={styles.dropdownItemButton} accessibilityLabel={I18n.t(text)}>
|
||||
<View style={styles.dropdownItemContainer}>
|
||||
<CustomIcon name={icon} size={22} color={themes[theme].bodyText} style={styles.dropdownItemIcon} />
|
||||
<Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text>
|
||||
{propType === itemType ? <Check /> : null}
|
||||
</View>
|
||||
</Touch>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const translateY = this.animatedValue.interpolate({
|
||||
const translateY = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-326, 0]
|
||||
});
|
||||
const { globalUsers, toggleWorkspace, isFederationEnabled, theme } = this.props;
|
||||
const backdropOpacity = this.animatedValue.interpolate({
|
||||
|
||||
const backdropOpacity = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, themes[theme].backdropOpacity]
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableWithoutFeedback onPress={this.close}>
|
||||
<TouchableWithoutFeedback onPress={close}>
|
||||
<Animated.View style={[styles.backdrop, { backgroundColor: themes[theme].backdropColor, opacity: backdropOpacity }]} />
|
||||
</TouchableWithoutFeedback>
|
||||
<Animated.View
|
||||
style={[styles.dropdownContainer, { transform: [{ translateY }], backgroundColor: themes[theme].backgroundColor }]}
|
||||
>
|
||||
<Touch onPress={this.close} accessibilityLabel={I18n.t('Search_by')}>
|
||||
<Touch onPress={close} accessibilityLabel={I18n.t('Search_by')}>
|
||||
<View
|
||||
style={[
|
||||
styles.dropdownContainerHeader,
|
||||
|
@ -109,9 +111,9 @@ export default class DirectoryOptions extends PureComponent<IDirectoryOptionsPro
|
|||
/>
|
||||
</View>
|
||||
</Touch>
|
||||
{this.renderItem('channels')}
|
||||
{this.renderItem('users')}
|
||||
{this.renderItem('teams')}
|
||||
{renderItem('channels')}
|
||||
{renderItem('users')}
|
||||
{renderItem('teams')}
|
||||
{isFederationEnabled ? (
|
||||
<>
|
||||
<View style={[styles.dropdownSeparator, { backgroundColor: themes[theme].separatorColor }]} />
|
||||
|
@ -132,4 +134,6 @@ export default class DirectoryOptions extends PureComponent<IDirectoryOptionsPro
|
|||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default DirectoryOptions;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { Component } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Linking } from 'react-native';
|
||||
import { batch, connect } from 'react-redux';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { batch, connect, useDispatch } from 'react-redux';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import * as List from '../../containers/List';
|
||||
import Button from '../../containers/Button';
|
||||
|
@ -14,7 +14,7 @@ import EventEmitter from '../../lib/methods/helpers/events';
|
|||
import ServerItem from '../../containers/ServerItem';
|
||||
import database from '../../lib/database';
|
||||
import { themes, TOKEN_KEY } from '../../lib/constants';
|
||||
import { withTheme } from '../../theme';
|
||||
import { useTheme } from '../../theme';
|
||||
import { localAuthenticate } from '../../lib/methods/helpers/localAuthentication';
|
||||
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
|
@ -30,65 +30,60 @@ const ROW_HEIGHT = 68;
|
|||
const ANIMATION_DURATION = 200;
|
||||
|
||||
interface IServerDropdownProps extends IBaseScreen<ChatsStackParamList, 'RoomsListView'> {
|
||||
insets?: {
|
||||
top: number;
|
||||
};
|
||||
closeServerDropdown: boolean;
|
||||
server: string;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
interface IServerDropdownState {
|
||||
servers: TServerModel[];
|
||||
}
|
||||
const ServerDropdown = ({ closeServerDropdown, server, isMasterDetail }: IServerDropdownProps) => {
|
||||
const animatedValue = useRef(new Animated.Value(0)).current;
|
||||
const subscription = useRef<Subscription>();
|
||||
const newServerTimeout = useRef<ReturnType<typeof setTimeout> | false>();
|
||||
const isMounted = useRef(false);
|
||||
|
||||
class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownState> {
|
||||
private animatedValue: Animated.Value;
|
||||
private subscription?: Subscription;
|
||||
private newServerTimeout?: ReturnType<typeof setTimeout> | false;
|
||||
const [servers, setServers] = useState<TServerModel[]>([]);
|
||||
|
||||
constructor(props: IServerDropdownProps) {
|
||||
super(props);
|
||||
this.state = { servers: [] };
|
||||
this.animatedValue = new Animated.Value(0);
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
const { theme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
async componentDidMount() {
|
||||
const serversDB = database.servers;
|
||||
const observable = await serversDB.get('servers').query().observeWithColumns(['name']);
|
||||
useEffect(() => {
|
||||
if (isMounted.current) close();
|
||||
}, [closeServerDropdown]);
|
||||
|
||||
this.subscription = observable.subscribe(data => {
|
||||
this.setState({ servers: data });
|
||||
});
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
Animated.timing(this.animatedValue, {
|
||||
toValue: 1,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
useNativeDriver: true
|
||||
}).start();
|
||||
}
|
||||
const init = async () => {
|
||||
const serversDB = database.servers;
|
||||
const observable = await serversDB.get('servers').query().observeWithColumns(['name']);
|
||||
|
||||
componentDidUpdate(prevProps: IServerDropdownProps) {
|
||||
const { closeServerDropdown } = this.props;
|
||||
if (prevProps.closeServerDropdown !== closeServerDropdown) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
subscription.current = observable.subscribe(data => {
|
||||
setServers(data);
|
||||
});
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.newServerTimeout) {
|
||||
clearTimeout(this.newServerTimeout);
|
||||
this.newServerTimeout = false;
|
||||
}
|
||||
if (this.subscription && this.subscription.unsubscribe) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
Animated.timing(animatedValue, {
|
||||
toValue: 1,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
useNativeDriver: true
|
||||
}).start();
|
||||
};
|
||||
init();
|
||||
|
||||
close = () => {
|
||||
const { dispatch } = this.props;
|
||||
Animated.timing(this.animatedValue, {
|
||||
return () => {
|
||||
if (newServerTimeout.current) {
|
||||
clearTimeout(newServerTimeout.current);
|
||||
newServerTimeout.current = false;
|
||||
}
|
||||
if (subscription.current && subscription.current.unsubscribe) {
|
||||
subscription.current.unsubscribe();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const close = () => {
|
||||
Animated.timing(animatedValue, {
|
||||
toValue: 0,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
|
@ -96,7 +91,7 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
|
|||
}).start(() => dispatch(toggleServerDropdown()));
|
||||
};
|
||||
|
||||
createWorkspace = async () => {
|
||||
const createWorkspace = async () => {
|
||||
logEvent(events.RL_CREATE_NEW_WORKSPACE);
|
||||
try {
|
||||
await Linking.openURL('https://cloud.rocket.chat/trial');
|
||||
|
@ -105,52 +100,49 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
|
|||
}
|
||||
};
|
||||
|
||||
navToNewServer = (previousServer: string) => {
|
||||
const { dispatch } = this.props;
|
||||
const navToNewServer = (previousServer: string) => {
|
||||
batch(() => {
|
||||
dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||
dispatch(serverInitAdd(previousServer));
|
||||
});
|
||||
};
|
||||
|
||||
addServer = () => {
|
||||
const addServer = () => {
|
||||
logEvent(events.RL_ADD_SERVER);
|
||||
const { server } = this.props;
|
||||
this.close();
|
||||
close();
|
||||
setTimeout(() => {
|
||||
this.navToNewServer(server);
|
||||
navToNewServer(server);
|
||||
}, ANIMATION_DURATION);
|
||||
};
|
||||
|
||||
select = async (server: string, version?: string) => {
|
||||
const { server: currentServer, dispatch, isMasterDetail } = this.props;
|
||||
this.close();
|
||||
if (currentServer !== server) {
|
||||
const select = async (serverParam: string, version?: string) => {
|
||||
close();
|
||||
if (server !== serverParam) {
|
||||
logEvent(events.RL_CHANGE_SERVER);
|
||||
const userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`);
|
||||
const userId = UserPreferences.getString(`${TOKEN_KEY}-${serverParam}`);
|
||||
if (isMasterDetail) {
|
||||
goRoom({ item: {}, isMasterDetail });
|
||||
}
|
||||
if (!userId) {
|
||||
setTimeout(() => {
|
||||
this.navToNewServer(currentServer);
|
||||
this.newServerTimeout = setTimeout(() => {
|
||||
EventEmitter.emit('NewServer', { server });
|
||||
navToNewServer(server);
|
||||
newServerTimeout.current = setTimeout(() => {
|
||||
EventEmitter.emit('NewServer', { server: serverParam });
|
||||
}, ANIMATION_DURATION);
|
||||
}, ANIMATION_DURATION);
|
||||
} else {
|
||||
await localAuthenticate(server);
|
||||
dispatch(selectServerRequest(server, version, true, true));
|
||||
await localAuthenticate(serverParam);
|
||||
dispatch(selectServerRequest(serverParam, version, true, true));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
remove = (server: string) =>
|
||||
const remove = (server: string) =>
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('This_will_remove_all_data_from_this_server'),
|
||||
confirmationText: I18n.t('Delete'),
|
||||
onPress: async () => {
|
||||
this.close();
|
||||
close();
|
||||
try {
|
||||
await removeServer({ server });
|
||||
} catch {
|
||||
|
@ -159,88 +151,83 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
|
|||
}
|
||||
});
|
||||
|
||||
renderServer = ({ item }: { item: { id: string; iconURL: string; name: string; version: string } }) => {
|
||||
const { server } = this.props;
|
||||
const renderServer = ({ item }: { item: { id: string; iconURL: string; name: string; version: string } }) => (
|
||||
<ServerItem
|
||||
item={item}
|
||||
onPress={() => select(item.id, item.version)}
|
||||
onLongPress={() => item.id === server || remove(item.id)}
|
||||
hasCheck={item.id === server}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ServerItem
|
||||
item={item}
|
||||
onPress={() => this.select(item.id, item.version)}
|
||||
onLongPress={() => item.id === server || this.remove(item.id)}
|
||||
hasCheck={item.id === server}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const maxRows = 4;
|
||||
const initialTop = 87 + Math.min(servers.length, maxRows) * ROW_HEIGHT;
|
||||
const statusBarHeight = insets?.top ?? 0;
|
||||
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
|
||||
|
||||
render() {
|
||||
const { servers } = this.state;
|
||||
const { theme, isMasterDetail, insets } = this.props;
|
||||
const maxRows = 4;
|
||||
const initialTop = 87 + Math.min(servers.length, maxRows) * ROW_HEIGHT;
|
||||
const statusBarHeight = insets?.top ?? 0;
|
||||
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
|
||||
const translateY = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-initialTop, heightDestination]
|
||||
});
|
||||
const backdropOpacity = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, themes[theme].backdropOpacity]
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<TouchableWithoutFeedback onPress={this.close}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
backgroundColor: themes[theme].backdropColor,
|
||||
opacity: backdropOpacity,
|
||||
top: heightDestination
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
const translateY = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-initialTop, heightDestination]
|
||||
});
|
||||
|
||||
const backdropOpacity = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, themes[theme].backdropOpacity]
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableWithoutFeedback onPress={close}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.dropdownContainer,
|
||||
styles.backdrop,
|
||||
{
|
||||
transform: [{ translateY }],
|
||||
backgroundColor: themes[theme].backgroundColor,
|
||||
borderColor: themes[theme].separatorColor
|
||||
backgroundColor: themes[theme].backdropColor,
|
||||
opacity: backdropOpacity,
|
||||
top: heightDestination
|
||||
}
|
||||
]}
|
||||
testID='rooms-list-header-server-dropdown'
|
||||
>
|
||||
<View style={[styles.dropdownContainerHeader, styles.serverHeader, { borderColor: themes[theme].separatorColor }]}>
|
||||
<Text style={[styles.serverHeaderText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Server')}</Text>
|
||||
<TouchableOpacity onPress={this.addServer} testID='rooms-list-header-server-add'>
|
||||
<Text style={[styles.serverHeaderAdd, { color: themes[theme].tintColor }]}>{I18n.t('Add_Server')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<FlatList
|
||||
style={{ maxHeight: maxRows * ROW_HEIGHT }}
|
||||
data={servers}
|
||||
keyExtractor={item => item.id}
|
||||
renderItem={this.renderServer}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
<List.Separator />
|
||||
<Button
|
||||
title={I18n.t('Create_a_new_workspace')}
|
||||
type='secondary'
|
||||
onPress={this.createWorkspace}
|
||||
testID='rooms-list-header-create-workspace-button'
|
||||
style={styles.buttonCreateWorkspace}
|
||||
color={themes[theme].tintColor}
|
||||
styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]}
|
||||
/>
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.dropdownContainer,
|
||||
{
|
||||
transform: [{ translateY }],
|
||||
backgroundColor: themes[theme].backgroundColor,
|
||||
borderColor: themes[theme].separatorColor
|
||||
}
|
||||
]}
|
||||
testID='rooms-list-header-server-dropdown'
|
||||
>
|
||||
<View style={[styles.dropdownContainerHeader, styles.serverHeader, { borderColor: themes[theme].separatorColor }]}>
|
||||
<Text style={[styles.serverHeaderText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Server')}</Text>
|
||||
<TouchableOpacity onPress={addServer} testID='rooms-list-header-server-add'>
|
||||
<Text style={[styles.serverHeaderAdd, { color: themes[theme].tintColor }]}>{I18n.t('Add_Server')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<FlatList
|
||||
style={{ maxHeight: maxRows * ROW_HEIGHT }}
|
||||
data={servers}
|
||||
keyExtractor={item => item.id}
|
||||
renderItem={renderServer}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
<List.Separator />
|
||||
<Button
|
||||
title={I18n.t('Create_a_new_workspace')}
|
||||
type='secondary'
|
||||
onPress={createWorkspace}
|
||||
testID='rooms-list-header-create-workspace-button'
|
||||
style={styles.buttonCreateWorkspace}
|
||||
color={themes[theme].tintColor}
|
||||
styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]}
|
||||
/>
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
closeServerDropdown: state.rooms.closeServerDropdown,
|
||||
|
@ -248,4 +235,4 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withSafeAreaInsets(withTheme(ServerDropdown)));
|
||||
export default connect(mapStateToProps)(ServerDropdown);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Animated, Easing, TouchableWithoutFeedback } from 'react-native';
|
||||
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../lib/constants';
|
||||
import { TSupportedThemes, withTheme } from '../../../theme';
|
||||
import { TSupportedThemes } from '../../../theme';
|
||||
import { headerHeight } from '../../../lib/methods/helpers/navigation';
|
||||
import * as List from '../../../containers/List';
|
||||
import { Filter } from '../filters';
|
||||
|
@ -16,32 +16,26 @@ const ANIMATION_DURATION = 200;
|
|||
interface IDropdownProps {
|
||||
isMasterDetail?: boolean;
|
||||
theme: TSupportedThemes;
|
||||
insets?: EdgeInsets;
|
||||
currentFilter: Filter;
|
||||
onClose: () => void;
|
||||
onFilterSelected: (value: Filter) => void;
|
||||
}
|
||||
|
||||
class Dropdown extends React.Component<IDropdownProps> {
|
||||
private animatedValue: Animated.Value;
|
||||
const Dropdown = ({ isMasterDetail, theme, currentFilter, onClose, onFilterSelected }: IDropdownProps) => {
|
||||
const animatedValue = useRef(new Animated.Value(0)).current;
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
constructor(props: IDropdownProps) {
|
||||
super(props);
|
||||
this.animatedValue = new Animated.Value(0);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Animated.timing(this.animatedValue, {
|
||||
useEffect(() => {
|
||||
Animated.timing(animatedValue, {
|
||||
toValue: 1,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
useNativeDriver: true
|
||||
}).start();
|
||||
}
|
||||
}, [animatedValue]);
|
||||
|
||||
close = () => {
|
||||
const { onClose } = this.props;
|
||||
Animated.timing(this.animatedValue, {
|
||||
const close = () => {
|
||||
Animated.timing(animatedValue, {
|
||||
toValue: 0,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
|
@ -49,51 +43,51 @@ class Dropdown extends React.Component<IDropdownProps> {
|
|||
}).start(() => onClose());
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isMasterDetail, insets, theme, currentFilter, onFilterSelected } = this.props;
|
||||
const statusBarHeight = insets?.top ?? 0;
|
||||
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
|
||||
const translateY = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-300, heightDestination] // approximated height of the component when closed/open
|
||||
});
|
||||
const backdropOpacity = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, themes[theme].backdropOpacity]
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<TouchableWithoutFeedback onPress={this.close}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
backgroundColor: themes[theme].backdropColor,
|
||||
opacity: backdropOpacity,
|
||||
top: heightDestination
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
const statusBarHeight = insets?.top ?? 0;
|
||||
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
|
||||
|
||||
const translateY = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-300, heightDestination] // approximated height of the component when closed/open
|
||||
});
|
||||
|
||||
const backdropOpacity = animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, themes[theme].backdropOpacity]
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<TouchableWithoutFeedback onPress={close}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.dropdownContainer,
|
||||
styles.backdrop,
|
||||
{
|
||||
transform: [{ translateY }],
|
||||
backgroundColor: themes[theme].backgroundColor,
|
||||
borderColor: themes[theme].separatorColor
|
||||
backgroundColor: themes[theme].backdropColor,
|
||||
opacity: backdropOpacity,
|
||||
top: heightDestination
|
||||
}
|
||||
]}
|
||||
>
|
||||
<DropdownItemHeader currentFilter={currentFilter} onPress={this.close} />
|
||||
<List.Separator />
|
||||
<DropdownItemFilter currentFilter={currentFilter} value={Filter.All} onPress={onFilterSelected} />
|
||||
<DropdownItemFilter currentFilter={currentFilter} value={Filter.Following} onPress={onFilterSelected} />
|
||||
<DropdownItemFilter currentFilter={currentFilter} value={Filter.Unread} onPress={onFilterSelected} />
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
/>
|
||||
</TouchableWithoutFeedback>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.dropdownContainer,
|
||||
{
|
||||
transform: [{ translateY }],
|
||||
backgroundColor: themes[theme].backgroundColor,
|
||||
borderColor: themes[theme].separatorColor
|
||||
}
|
||||
]}
|
||||
>
|
||||
<DropdownItemHeader currentFilter={currentFilter} onPress={close} />
|
||||
<List.Separator />
|
||||
<DropdownItemFilter currentFilter={currentFilter} value={Filter.All} onPress={onFilterSelected} />
|
||||
<DropdownItemFilter currentFilter={currentFilter} value={Filter.Following} onPress={onFilterSelected} />
|
||||
<DropdownItemFilter currentFilter={currentFilter} value={Filter.Unread} onPress={onFilterSelected} />
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default withTheme(withSafeAreaInsets(Dropdown));
|
||||
export default Dropdown;
|
||||
|
|
Loading…
Reference in New Issue