Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW] Action Sheet #2114

Merged
merged 109 commits into from
Jun 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
802257e
[WIP] New Action Sheet
djorkaeffalexandre May 12, 2020
aa6168c
[NEW] Header Indicator
djorkaeffalexandre May 12, 2020
0e50e90
[IMPROVEMENT] Header Logic
djorkaeffalexandre May 12, 2020
d75d902
[NEW] Use EventEmitter to show ActionSheet for while
djorkaeffalexandre May 12, 2020
3175fad
[FIX] Animation
djorkaeffalexandre May 12, 2020
9d560ba
[IMPROVEMENT] Use provider
djorkaeffalexandre May 12, 2020
33c74f2
[FIX] Add callback
djorkaeffalexandre May 12, 2020
cf12576
[FIX] Message Actions
djorkaeffalexandre May 12, 2020
c7ccaf8
[FIX] Add MessageActions icons
djorkaeffalexandre May 12, 2020
7845f06
[NEW] MessageErrorActions
djorkaeffalexandre May 12, 2020
9da61ba
[IMPROVEMENT] OnClose
djorkaeffalexandre May 12, 2020
8df08f5
[FIX] Adjust height
djorkaeffalexandre May 12, 2020
39c1b7f
[FIX] Close/Reopen
djorkaeffalexandre May 12, 2020
95bf8f5
[CHORE] Remove react-native-action-sheet
djorkaeffalexandre May 12, 2020
e482823
[CHORE] Move ActionSheet
djorkaeffalexandre May 12, 2020
02900e5
[FIX] Reply Message
djorkaeffalexandre May 12, 2020
e47923f
[IMPROVEMENT] Hide ActionSheet logic
djorkaeffalexandre May 12, 2020
7ae9aef
[WIP] Custom MessageActions Header
djorkaeffalexandre May 12, 2020
310d24a
[IMPROVEMENT] MessageActions Header
djorkaeffalexandre May 12, 2020
90ab8de
[IMPROVEMENT] Enable Scroll
djorkaeffalexandre May 12, 2020
bf0312a
[FIX] Scroll on Android
djorkaeffalexandre May 13, 2020
8a20cbb
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
djorkaeffalexandre May 14, 2020
4208c72
Move to react-native-scroll-bottom-sheet
djorkaeffalexandre May 20, 2020
8c12946
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
djorkaeffalexandre May 20, 2020
672f9e4
Stash
djorkaeffalexandre May 20, 2020
68abfde
Merge develop
djorkaeffalexandre May 20, 2020
cc3f820
Refactor actions
djorkaeffalexandre May 20, 2020
50806d7
Revert some changes
djorkaeffalexandre May 21, 2020
6c6e361
Trying react-native-modalize
djorkaeffalexandre May 21, 2020
a89856c
Back to HOC
djorkaeffalexandre May 21, 2020
b9eb801
ActionSheet style
djorkaeffalexandre May 21, 2020
9f4461c
HOC Header
djorkaeffalexandre May 21, 2020
6515e41
Reaction actionSheet
djorkaeffalexandre May 21, 2020
d0824bb
Fix messageBox actions
djorkaeffalexandre May 21, 2020
1bc67f6
Fix add reaction
djorkaeffalexandre May 21, 2020
d95730f
Change to flatListProps
djorkaeffalexandre May 21, 2020
c677322
fix modalize android scroll
djorkaeffalexandre May 22, 2020
639520b
Use react-native-scroll-bottom-sheet
djorkaeffalexandre May 22, 2020
7b8f59f
[NEW] BottomSheet dismissable & [FIX] Android not opening
djorkaeffalexandre May 25, 2020
8c48da1
[NEW] Show emojis based on screen width
djorkaeffalexandre May 25, 2020
5e6c720
[WIP] Adjust to content height
djorkaeffalexandre May 25, 2020
e4cbc92
[IMPROVEMENT] Responsible
djorkaeffalexandre May 25, 2020
355a646
Merge branch 'develop' into new.action-sheet
djorkaeffalexandre May 25, 2020
c9dafd2
[IMPROVEMENT] Use alert instead actionSheet at NewServerView
djorkaeffalexandre May 25, 2020
933c954
[FIX] Handle tablet cases
djorkaeffalexandre May 25, 2020
0d65113
[IMPROVEMENT] Remove actionSheet of RoomMembersView
djorkaeffalexandre May 25, 2020
66cde0a
[IMPROVEMENT] Min snap distance when its portrait
djorkaeffalexandre May 25, 2020
11b7b28
[CHORE] Remove unused Components
djorkaeffalexandre May 25, 2020
4200583
[IMPROVEMENT] Remove duplicated add-reaction
djorkaeffalexandre May 25, 2020
6dd2af5
Merge branch 'new.action-sheet' of https://github.com/RocketChat/Rock…
djorkaeffalexandre May 25, 2020
3a4ff61
[IMPROVEMENT] Refactor Icon Package
djorkaeffalexandre May 26, 2020
cfc95e0
Merge branch 'new.icons' into new.action-sheet
djorkaeffalexandre May 26, 2020
aa12751
[IMPROVEMENT] Use new icons
djorkaeffalexandre May 26, 2020
54330a7
[FIX] Select message at MessageActions before getOptions
djorkaeffalexandre May 26, 2020
c177c0f
[FIX] Custom header height
djorkaeffalexandre May 26, 2020
e30149a
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
djorkaeffalexandre May 26, 2020
b7b8501
[CHORE] Remove patch & [FIX] Tablet bottom sheet
djorkaeffalexandre May 26, 2020
feab6a6
[FIX] Use ListItem height to BottomSheet Height
djorkaeffalexandre May 26, 2020
52d5781
Some fixes
djorkaeffalexandre May 27, 2020
f86798b
[FIX] Custom MessageActions header
djorkaeffalexandre May 27, 2020
ce47856
[FIX] Android height adjust
djorkaeffalexandre May 27, 2020
90ef49c
[IMPROVEMENT] Item touchable & [FIX] Respect pin permission
djorkaeffalexandre May 27, 2020
34ed411
[IMPROVEMENT] More than one snap point
djorkaeffalexandre May 27, 2020
c3d1867
some size fixes
djorkaeffalexandre May 27, 2020
55887d4
Merge branch 'develop' into new.icons
djorkaeffalexandre May 27, 2020
e00bc4b
Merge branch 'new.icons' into new.action-sheet
djorkaeffalexandre May 27, 2020
a114c88
improve code
djorkaeffalexandre May 27, 2020
2bd6f2b
hide horizontal scroll indicator
djorkaeffalexandre May 27, 2020
d840408
[FIX] Focus MessageBox on edit message
djorkaeffalexandre May 28, 2020
6236017
[FIX] Ripple color
djorkaeffalexandre May 28, 2020
61d570d
[IMPROVEMENT] Backdrop must keep same opacity after 50% of the screen
djorkaeffalexandre May 28, 2020
e1f328e
[TEST] Change animation config
djorkaeffalexandre May 28, 2020
967b73d
[IMPROVEMENT] BackHandler should close the ActionSheet
djorkaeffalexandre May 28, 2020
1dde0af
[CHORE] Add react-native-safe-area-context
djorkaeffalexandre May 28, 2020
2bc51c8
[FIX] Provide a bottom padding at notch devices
djorkaeffalexandre May 28, 2020
2abede3
[IMPROVEMENT] Improve backdrop input/output range
djorkaeffalexandre May 28, 2020
91c2fbb
[FIX] Weird Android Snap behavior
djorkaeffalexandre May 29, 2020
a61ab22
[PATCH] React-native-scroll-bottom-sheet
djorkaeffalexandre May 29, 2020
dad8752
[CI] Re-run build
djorkaeffalexandre May 29, 2020
a8e02bb
[FIX] Landscape android
djorkaeffalexandre May 29, 2020
ccd76eb
[IMPROVEMENT] Cover 50% of the screen at the landscape mode
djorkaeffalexandre May 29, 2020
e47ed1f
[FIX] Adjust emoji content to width size
djorkaeffalexandre May 29, 2020
521e364
[IMPROVEMENT] Use hooks library
djorkaeffalexandre May 29, 2020
8749ea0
[IMPROVEMENT] Close the actionSheet when orientation change
djorkaeffalexandre May 29, 2020
dedd508
deactivate safe-area-context for while
djorkaeffalexandre May 29, 2020
bcaf4a3
[REVERT] Re-add react-native-safe-area-context (3.0.2)
djorkaeffalexandre Jun 1, 2020
80cbd10
[IMPROVEMENT] Use focused background
djorkaeffalexandre Jun 1, 2020
ad786ac
[TESTS] E2E Tests updated to new BottomSheet
djorkaeffalexandre Jun 2, 2020
83aceee
[NEW] Add cancel button
djorkaeffalexandre Jun 2, 2020
b1ae5c9
[FIX] Cancel button at android
djorkaeffalexandre Jun 2, 2020
59a3ecc
[IMPROVEMENT] Use cancelable bottom sheet at room members view
djorkaeffalexandre Jun 2, 2020
1fe9fcd
Merge branch 'develop' into new.action-sheet
diegolmello Jun 5, 2020
1cc6988
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
djorkaeffalexandre Jun 5, 2020
811c160
[IMPROVEMENT] Use better function names
djorkaeffalexandre Jun 5, 2020
34c47e0
Merge branch 'new.action-sheet' of https://github.com/RocketChat/Rock…
djorkaeffalexandre Jun 5, 2020
c2d2cec
[IMPROVEMENT] Use getItemLayout
djorkaeffalexandre Jun 8, 2020
c3fb259
[FIX][TEMP] Animation
djorkaeffalexandre Jun 8, 2020
9ec87dc
Review
djorkaeffalexandre Jun 8, 2020
88940ed
Build
djorkaeffalexandre Jun 9, 2020
200bf4a
Merge branch 'develop' into new.action-sheet
diegolmello Jun 15, 2020
f94c696
Header keyExtractor
diegolmello Jun 15, 2020
bd1547f
Rename function
diegolmello Jun 15, 2020
4ded4fa
Tweak animation
diegolmello Jun 15, 2020
8f6b598
Refactoring
diegolmello Jun 15, 2020
74f7a96
useTheme
diegolmello Jun 15, 2020
a500845
Refactoring
diegolmello Jun 15, 2020
070a071
TestIDs
diegolmello Jun 15, 2020
942f772
Refactor
diegolmello Jun 15, 2020
66c2d43
Remove old lib
diegolmello Jun 15, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 50 additions & 47 deletions app/AppContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navi
import {
ROOT_LOADING, ROOT_OUTSIDE, ROOT_NEW_SERVER, ROOT_INSIDE, ROOT_SET_USERNAME, ROOT_BACKGROUND
} from './actions/app';
import { ActionSheetProvider } from './containers/ActionSheet';

// Stacks
import AuthLoadingView from './views/AuthLoadingView';
Expand Down Expand Up @@ -53,53 +54,55 @@ const App = React.memo(({ root, isMasterDetail }) => {

return (
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<NavigationContainer
theme={navTheme}
ref={Navigation.navigationRef}
onStateChange={(state) => {
const previousRouteName = Navigation.routeNameRef.current;
const currentRouteName = getActiveRouteName(state);
if (previousRouteName !== currentRouteName) {
setCurrentScreen(currentRouteName);
}
Navigation.routeNameRef.current = currentRouteName;
}}
>
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
<>
{root === ROOT_LOADING || root === ROOT_BACKGROUND ? (
<Stack.Screen
name='AuthLoading'
component={AuthLoadingView}
/>
) : null}
{root === ROOT_OUTSIDE || root === ROOT_NEW_SERVER ? (
<Stack.Screen
name='OutsideStack'
component={OutsideStack}
/>
) : null}
{root === ROOT_INSIDE && isMasterDetail ? (
<Stack.Screen
name='MasterDetailStack'
component={MasterDetailStack}
/>
) : null}
{root === ROOT_INSIDE && !isMasterDetail ? (
<Stack.Screen
name='InsideStack'
component={InsideStack}
/>
) : null}
{root === ROOT_SET_USERNAME ? (
<Stack.Screen
name='SetUsernameStack'
component={SetUsernameStack}
/>
) : null}
</>
</Stack.Navigator>
</NavigationContainer>
<ActionSheetProvider>
<NavigationContainer
theme={navTheme}
ref={Navigation.navigationRef}
onStateChange={(state) => {
const previousRouteName = Navigation.routeNameRef.current;
const currentRouteName = getActiveRouteName(state);
if (previousRouteName !== currentRouteName) {
setCurrentScreen(currentRouteName);
}
Navigation.routeNameRef.current = currentRouteName;
}}
>
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
<>
{root === ROOT_LOADING || root === ROOT_BACKGROUND ? (
<Stack.Screen
name='AuthLoading'
component={AuthLoadingView}
/>
) : null}
{root === ROOT_OUTSIDE || root === ROOT_NEW_SERVER ? (
<Stack.Screen
name='OutsideStack'
component={OutsideStack}
/>
) : null}
{root === ROOT_INSIDE && isMasterDetail ? (
<Stack.Screen
name='MasterDetailStack'
component={MasterDetailStack}
/>
) : null}
{root === ROOT_INSIDE && !isMasterDetail ? (
<Stack.Screen
name='InsideStack'
component={InsideStack}
/>
) : null}
{root === ROOT_SET_USERNAME ? (
<Stack.Screen
name='SetUsernameStack'
component={SetUsernameStack}
/>
) : null}
</>
</Stack.Navigator>
</NavigationContainer>
</ActionSheetProvider>
</SafeAreaProvider>
);
});
Expand Down
214 changes: 214 additions & 0 deletions app/containers/ActionSheet/ActionSheet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import React, {
useRef,
useState,
useEffect,
forwardRef,
useImperativeHandle,
useCallback,
isValidElement
} from 'react';
import PropTypes from 'prop-types';
import { Keyboard, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { TapGestureHandler, State } from 'react-native-gesture-handler';
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
import Animated, {
Extrapolate,
interpolate,
Value,
Easing
} from 'react-native-reanimated';
import * as Haptics from 'expo-haptics';
import {
useDimensions,
useBackHandler,
useDeviceOrientation
} from '@react-native-community/hooks';

import { Item } from './Item';
import { Handle } from './Handle';
import { Button } from './Button';
import { themes } from '../../constants/colors';
import styles, { ITEM_HEIGHT } from './styles';
import { isTablet, isIOS } from '../../utils/deviceInfo';
import Separator from '../Separator';
import I18n from '../../i18n';

const getItemLayout = (data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index });

const HANDLE_HEIGHT = isIOS ? 40 : 56;
const MAX_SNAP_HEIGHT = 16;
const CANCEL_HEIGHT = 64;

const ANIMATION_DURATION = 250;

const ANIMATION_CONFIG = {
duration: ANIMATION_DURATION,
// https://easings.net/#easeInOutCubic
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0)
};

const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
const bottomSheetRef = useRef();
const [data, setData] = useState({});
const [isVisible, setVisible] = useState(false);
const orientation = useDeviceOrientation();
const { height } = useDimensions().window;
const insets = useSafeAreaInsets();
const { landscape } = orientation;

const maxSnap = Math.max(
(
height
// Items height
- (ITEM_HEIGHT * (data?.options?.length || 0))
// Handle height
- HANDLE_HEIGHT
// Custom header height
- (data?.headerHeight || 0)
// Insets bottom height (Notch devices)
- insets.bottom
// Cancel button height
- (data?.hasCancel ? CANCEL_HEIGHT : 0)
),
MAX_SNAP_HEIGHT
);

/*
* if the action sheet cover more
* than 60% of the whole screen
* and it's not at the landscape mode
* we'll provide more one snap
* that point 50% of the whole screen
*/
const snaps = (height - maxSnap > height * 0.6) && !landscape ? [maxSnap, height * 0.5, height] : [maxSnap, height];
const openedSnapIndex = snaps.length > 2 ? 1 : 0;
const closedSnapIndex = snaps.length - 1;

const toggleVisible = () => setVisible(!isVisible);

const hide = () => {
bottomSheetRef.current?.snapTo(closedSnapIndex);
};

const show = (options) => {
setData(options);
toggleVisible();
};

const onBackdropPressed = ({ nativeEvent }) => {
if (nativeEvent.oldState === State.ACTIVE) {
hide();
}
};

useBackHandler(() => {
if (isVisible) {
hide();
}
return isVisible;
});

useEffect(() => {
if (isVisible) {
Keyboard.dismiss();
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
bottomSheetRef.current?.snapTo(openedSnapIndex);
}
}, [isVisible]);

// Hides action sheet when orientation changes
useEffect(() => {
setVisible(false);
}, [orientation.landscape]);

useImperativeHandle(ref, () => ({
showActionSheet: show,
hideActionSheet: hide
}));

const renderHandle = useCallback(() => (
<>
<Handle theme={theme} />
{isValidElement(data?.customHeader) ? data.customHeader : null}
</>
));

const renderFooter = useCallback(() => (data?.hasCancel ? (
<Button
onPress={hide}
style={[styles.button, { backgroundColor: themes[theme].auxiliaryBackground }]}
theme={theme}
>
<Text style={[styles.text, { color: themes[theme].bodyText }]}>
{I18n.t('Cancel')}
</Text>
</Button>
) : null));

const renderSeparator = useCallback(() => <Separator theme={theme} style={styles.separator} />);

const renderItem = useCallback(({ item }) => <Item item={item} hide={hide} theme={theme} />);

const animatedPosition = React.useRef(new Value(0));
const opacity = interpolate(animatedPosition.current, {
inputRange: [0, 1],
outputRange: [0, 0.7],
extrapolate: Extrapolate.CLAMP
});

return (
<>
{children}
{isVisible && (
<>
<TapGestureHandler onHandlerStateChange={onBackdropPressed}>
<Animated.View
testID='action-sheet-backdrop'
style={[
styles.backdrop,
{
backgroundColor: themes[theme].backdropColor,
opacity
}
]}
/>
</TapGestureHandler>
<ScrollBottomSheet
testID='action-sheet'
ref={bottomSheetRef}
componentType='FlatList'
snapPoints={snaps}
initialSnapIndex={closedSnapIndex}
renderHandle={renderHandle}
onSettle={index => (index === closedSnapIndex) && toggleVisible()}
animatedPosition={animatedPosition.current}
containerStyle={[
styles.container,
{ backgroundColor: themes[theme].focusedBackground },
(landscape || isTablet) && styles.bottomSheet
]}
animationConfig={ANIMATION_CONFIG}
// FlatList props
data={data?.options}
renderItem={renderItem}
keyExtractor={item => item.title}
style={{ backgroundColor: themes[theme].focusedBackground }}
contentContainerStyle={styles.content}
ItemSeparatorComponent={renderSeparator}
ListHeaderComponent={renderSeparator}
ListFooterComponent={renderFooter}
getItemLayout={getItemLayout}
removeClippedSubviews={isIOS}
/>
</>
)}
</>
);
}));
ActionSheet.propTypes = {
children: PropTypes.node,
theme: PropTypes.string
};

export default ActionSheet;
7 changes: 7 additions & 0 deletions app/containers/ActionSheet/Button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { TouchableOpacity } from 'react-native';

import { isAndroid } from '../../utils/deviceInfo';
import Touch from '../../utils/touch';

// Taken from https://github.com/rgommezz/react-native-scroll-bottom-sheet#touchables
export const Button = isAndroid ? Touch : TouchableOpacity;
15 changes: 15 additions & 0 deletions app/containers/ActionSheet/Handle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';

import styles from './styles';
import { themes } from '../../constants/colors';

export const Handle = React.memo(({ theme }) => (
<View style={[styles.handle, { backgroundColor: themes[theme].focusedBackground }]} testID='action-sheet-handle'>
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
</View>
));
Handle.propTypes = {
theme: PropTypes.string
};
41 changes: 41 additions & 0 deletions app/containers/ActionSheet/Item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Text } from 'react-native';

import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons';
import styles from './styles';
import { Button } from './Button';

export const Item = React.memo(({ item, hide, theme }) => {
const onPress = () => {
hide();
item?.onPress();
};

return (
<Button
onPress={onPress}
style={[styles.item, { backgroundColor: themes[theme].focusedBackground }]}
theme={theme}
>
<CustomIcon name={item.icon} size={20} color={item.danger ? themes[theme].dangerColor : themes[theme].bodyText} />
<Text
numberOfLines={1}
style={[styles.title, { color: item.danger ? themes[theme].dangerColor : themes[theme].bodyText }]}
>
{item.title}
</Text>
</Button>
);
});
Item.propTypes = {
item: PropTypes.shape({
title: PropTypes.string,
icon: PropTypes.string,
danger: PropTypes.bool,
onPress: PropTypes.func
}),
hide: PropTypes.func,
theme: PropTypes.string
};
Loading