[NEW] Update room item animations (#4024)

* Upgrade react-native-gesture-handler to 2.3.0

* Update room item animations to reanimated v2

* Add Parallax animation on fav and hide buttons and additional swipe gesture to toggleFav

* Fix tests

* Ignore typescript error for setTimeout function

* Update pods

* Fix blank area on swiping all the way right/left

* Fix Action Buttons on devices with notch

* Update snapshot

* Use colors from useTheme

* Destructure props

* Proper types for nativeEvent and event

* Remove toggleFav gesture

* Clean bits

* Fix lint error

* Fix position of Room Action Buttons on MasterDetail

* Remove comment

* Update animations logic

* Add haptic feedback on swipe

* Add haptic feedback on unswipe gesture

* Update react-native-gesture-handler to 2.4.2

* update pods

* Migrating off RNGHEnabledRootView

* Update types to ReturnType<typeof setTimeout>

Co-authored-by: GleidsonDaniel <gleidson10daniel@hotmail.com>
This commit is contained in:
Danish Ahmed Mirza 2022-06-06 18:53:49 +05:30 committed by GitHub
parent c76a1e6a4c
commit 02c1bc50b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 360 additions and 360 deletions

View File

@ -12,7 +12,6 @@ import com.facebook.react.ReactRootView;
import com.facebook.react.ReactActivityDelegate; import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactFragmentActivity; import com.facebook.react.ReactFragmentActivity;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import com.zoontek.rnbootsplash.RNBootSplash; import com.zoontek.rnbootsplash.RNBootSplash;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -51,16 +50,6 @@ public class MainActivity extends ReactFragmentActivity {
return "RocketChatRN"; return "RocketChatRN";
} }
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
};
}
// from react-native-orientation // from react-native-orientation
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {

View File

@ -3,21 +3,10 @@ package chat.rocket.reactnative.share;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate; import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView; import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
public class ShareActivity extends ReactActivity { public class ShareActivity extends ReactActivity {
@Override @Override
protected String getMainComponentName() { protected String getMainComponentName() {
return "ShareRocketChatRN"; return "ShareRocketChatRN";
} }
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(ShareActivity.this);
}
};
}
} }

View File

@ -1,25 +1,32 @@
import React from 'react'; import React from 'react';
import { Animated, View } from 'react-native'; import { View } from 'react-native';
import Animated, {
useAnimatedStyle,
interpolate,
withSpring,
runOnJS,
useAnimatedReaction,
useSharedValue
} from 'react-native-reanimated';
import { RectButton } from 'react-native-gesture-handler'; import { RectButton } from 'react-native-gesture-handler';
import * as Haptics from 'expo-haptics';
import { isRTL } from '../../i18n';
import { CustomIcon } from '../CustomIcon'; import { CustomIcon } from '../CustomIcon';
import { DisplayMode, themes } from '../../lib/constants'; import { DisplayMode } from '../../lib/constants';
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles'; import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
import { ILeftActionsProps, IRightActionsProps } from './interfaces'; import { ILeftActionsProps, IRightActionsProps } from './interfaces';
import { useTheme } from '../../theme';
import I18n from '../../i18n';
const reverse = new Animated.Value(isRTL() ? -1 : 1);
const CONDENSED_ICON_SIZE = 24; const CONDENSED_ICON_SIZE = 24;
const EXPANDED_ICON_SIZE = 28; const EXPANDED_ICON_SIZE = 28;
export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress, displayMode }: ILeftActionsProps) => { export const LeftActions = React.memo(({ transX, isRead, width, onToggleReadPress, displayMode }: ILeftActionsProps) => {
const translateX = Animated.multiply( const { colors } = useTheme();
transX.interpolate({
inputRange: [0, ACTION_WIDTH], const animatedStyles = useAnimatedStyle(() => ({
outputRange: [-ACTION_WIDTH, 0] transform: [{ translateX: transX.value }]
}), }));
reverse
);
const isCondensed = displayMode === DisplayMode.Condensed; const isCondensed = displayMode === DisplayMode.Condensed;
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null; const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
@ -29,20 +36,16 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
<Animated.View <Animated.View
style={[ style={[
styles.actionLeftButtonContainer, styles.actionLeftButtonContainer,
{ { width: width * 2, backgroundColor: colors.tintColor, right: '100%' },
right: width - ACTION_WIDTH, viewHeight,
width, animatedStyles
transform: [{ translateX }],
backgroundColor: themes[theme].tintColor
},
viewHeight
]}> ]}>
<View style={[styles.actionLeftButtonContainer, viewHeight]}> <View style={[styles.actionLeftButtonContainer, viewHeight]}>
<RectButton style={styles.actionButton} onPress={onToggleReadPress}> <RectButton style={styles.actionButton} onPress={onToggleReadPress}>
<CustomIcon <CustomIcon
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE} size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
name={isRead ? 'flag' : 'check'} name={isRead ? 'flag' : 'check'}
color={themes[theme].buttonText} color={colors.buttonText}
/> />
</RectButton> </RectButton>
</View> </View>
@ -51,64 +54,102 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
); );
}); });
export const RightActions = React.memo( export const RightActions = React.memo(({ transX, favorite, width, toggleFav, onHidePress, displayMode }: IRightActionsProps) => {
({ transX, favorite, width, toggleFav, onHidePress, theme, displayMode }: IRightActionsProps) => { const { colors } = useTheme();
const translateXFav = Animated.multiply(
transX.interpolate({
inputRange: [-width / 2, -ACTION_WIDTH * 2, 0],
outputRange: [width / 2, width - ACTION_WIDTH * 2, width]
}),
reverse
);
const translateXHide = Animated.multiply(
transX.interpolate({
inputRange: [-width, -LONG_SWIPE, -ACTION_WIDTH * 2, 0],
outputRange: [0, width - LONG_SWIPE, width - ACTION_WIDTH, width]
}),
reverse
);
const isCondensed = displayMode === DisplayMode.Condensed; const animatedFavStyles = useAnimatedStyle(() => ({ transform: [{ translateX: transX.value }] }));
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
return ( const translateXHide = useSharedValue(0);
<View style={[styles.actionsLeftContainer, viewHeight]} pointerEvents='box-none'>
<Animated.View const triggerHideAnimation = (toValue: number) => {
style={[ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
styles.actionRightButtonContainer, translateXHide.value = withSpring(toValue, { overshootClamping: true, mass: 0.7 });
{ };
width,
transform: [{ translateX: translateXFav }], useAnimatedReaction(
backgroundColor: themes[theme].hideBackground () => transX.value,
}, (currentTransX, previousTransX) => {
viewHeight // Triggers the animation and hapticFeedback if swipe reaches/unreaches the threshold.
]}> if (I18n.isRTL) {
<RectButton style={[styles.actionButton, { backgroundColor: themes[theme].favoriteBackground }]} onPress={toggleFav}> if (previousTransX && currentTransX > LONG_SWIPE && previousTransX <= LONG_SWIPE) {
<CustomIcon runOnJS(triggerHideAnimation)(ACTION_WIDTH);
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE} } else if (previousTransX && currentTransX <= LONG_SWIPE && previousTransX > LONG_SWIPE) {
name={favorite ? 'star-filled' : 'star'} runOnJS(triggerHideAnimation)(0);
color={themes[theme].buttonText} }
/> } else if (previousTransX && currentTransX < -LONG_SWIPE && previousTransX >= -LONG_SWIPE) {
</RectButton> runOnJS(triggerHideAnimation)(-ACTION_WIDTH);
</Animated.View> } else if (previousTransX && currentTransX >= -LONG_SWIPE && previousTransX < -LONG_SWIPE) {
<Animated.View runOnJS(triggerHideAnimation)(0);
style={[ }
styles.actionRightButtonContainer, }
{ );
width,
transform: [{ translateX: translateXHide }] const animatedHideStyles = useAnimatedStyle(() => {
}, if (I18n.isRTL) {
isCondensed && { height: ROW_HEIGHT_CONDENSED } if (transX.value < LONG_SWIPE && transX.value >= 2 * ACTION_WIDTH) {
]}> const parallaxSwipe = interpolate(
<RectButton style={[styles.actionButton, { backgroundColor: themes[theme].hideBackground }]} onPress={onHidePress}> transX.value,
<CustomIcon [2 * ACTION_WIDTH, LONG_SWIPE],
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE} [ACTION_WIDTH, ACTION_WIDTH + 0.1 * transX.value]
name='unread-on-top-disabled' );
color={themes[theme].buttonText} return { transform: [{ translateX: parallaxSwipe + translateXHide.value }] };
/> }
</RectButton> return { transform: [{ translateX: transX.value - ACTION_WIDTH + translateXHide.value }] };
</Animated.View> }
</View> if (transX.value > -LONG_SWIPE && transX.value <= -2 * ACTION_WIDTH) {
); const parallaxSwipe = interpolate(
} transX.value,
); [-2 * ACTION_WIDTH, -LONG_SWIPE],
[-ACTION_WIDTH, -ACTION_WIDTH + 0.1 * transX.value]
);
return { transform: [{ translateX: parallaxSwipe + translateXHide.value }] };
}
return { transform: [{ translateX: transX.value + ACTION_WIDTH + translateXHide.value }] };
});
const isCondensed = displayMode === DisplayMode.Condensed;
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
return (
<View style={[styles.actionsLeftContainer, viewHeight]} pointerEvents='box-none'>
<Animated.View
style={[
styles.actionRightButtonContainer,
{
width,
backgroundColor: colors.favoriteBackground,
left: '100%'
},
viewHeight,
animatedFavStyles
]}>
<RectButton style={[styles.actionButton, { backgroundColor: colors.favoriteBackground }]} onPress={toggleFav}>
<CustomIcon
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
name={favorite ? 'star-filled' : 'star'}
color={colors.buttonText}
/>
</RectButton>
</Animated.View>
<Animated.View
style={[
styles.actionRightButtonContainer,
{
width: width * 2,
backgroundColor: colors.hideBackground,
left: '100%'
},
isCondensed && { height: ROW_HEIGHT_CONDENSED },
animatedHideStyles
]}>
<RectButton style={[styles.actionButton, { backgroundColor: colors.hideBackground }]} onPress={onHidePress}>
<CustomIcon
size={isCondensed ? CONDENSED_ICON_SIZE : EXPANDED_ICON_SIZE}
name='unread-on-top-disabled'
color={colors.buttonText}
/>
</RectButton>
</Animated.View>
</View>
);
});

View File

@ -66,7 +66,6 @@ const RoomItem = ({
hideChannel={hideChannel} hideChannel={hideChannel}
testID={testID} testID={testID}
type={type} type={type}
theme={theme}
isFocused={isFocused} isFocused={isFocused}
swipeEnabled={swipeEnabled} swipeEnabled={swipeEnabled}
displayMode={displayMode}> displayMode={displayMode}>

View File

@ -1,207 +1,98 @@
import React from 'react'; import React from 'react';
import { Animated } from 'react-native'; import Animated, {
useAnimatedGestureHandler,
useSharedValue,
useAnimatedStyle,
withSpring,
runOnJS
} from 'react-native-reanimated';
import { import {
GestureEvent,
HandlerStateChangeEventPayload,
LongPressGestureHandler, LongPressGestureHandler,
PanGestureHandler, PanGestureHandler,
PanGestureHandlerEventPayload, State,
State HandlerStateChangeEventPayload,
PanGestureHandlerEventPayload
} from 'react-native-gesture-handler'; } from 'react-native-gesture-handler';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles'; import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles';
import { isRTL } from '../../i18n';
import { themes } from '../../lib/constants';
import { LeftActions, RightActions } from './Actions'; import { LeftActions, RightActions } from './Actions';
import { ITouchableProps } from './interfaces'; import { ITouchableProps } from './interfaces';
import { useTheme } from '../../theme';
import I18n from '../../i18n';
class Touchable extends React.Component<ITouchableProps, any> { const Touchable = ({
private dragX: Animated.Value; children,
private rowOffSet: Animated.Value; type,
private reverse: Animated.Value; onPress,
private transX: Animated.AnimatedAddition; onLongPress,
private transXReverse: Animated.AnimatedMultiplication; testID,
private _onGestureEvent: (event: GestureEvent<PanGestureHandlerEventPayload>) => void; width,
private _value: number; favorite,
isRead,
rid,
toggleFav,
toggleRead,
hideChannel,
isFocused,
swipeEnabled,
displayMode
}: ITouchableProps): React.ReactElement => {
const { theme, colors } = useTheme();
constructor(props: ITouchableProps) { const rowOffSet = useSharedValue(0);
super(props); const transX = useSharedValue(0);
this.dragX = new Animated.Value(0); const rowState = useSharedValue(0); // 0: closed, 1: right opened, -1: left opened
this.rowOffSet = new Animated.Value(0); let _value = 0;
this.reverse = new Animated.Value(isRTL() ? -1 : 1);
this.transX = Animated.add(this.rowOffSet, this.dragX);
this.transXReverse = Animated.multiply(this.transX, this.reverse);
this.state = {
rowState: 0 // 0: closed, 1: right opened, -1: left opened
};
this._onGestureEvent = Animated.event([{ nativeEvent: { translationX: this.dragX } }], { useNativeDriver: true });
this._value = 0;
}
_onHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload & PanGestureHandlerEventPayload }) => { const close = () => {
if (nativeEvent.oldState === State.ACTIVE) { rowState.value = 0;
this._handleRelease(nativeEvent); transX.value = withSpring(0, { overshootClamping: true });
} rowOffSet.value = 0;
}; };
onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => { const handleToggleFav = () => {
if (nativeEvent.state === State.ACTIVE) {
this.onLongPress();
}
};
_handleRelease = (nativeEvent: PanGestureHandlerEventPayload) => {
const { translationX } = nativeEvent;
const { rowState } = this.state;
this._value += translationX;
let toValue = 0;
if (rowState === 0) {
// if no option is opened
if (translationX > 0 && translationX < LONG_SWIPE) {
// open leading option if he swipe right but not enough to trigger action
if (isRTL()) {
toValue = 2 * ACTION_WIDTH;
} else {
toValue = ACTION_WIDTH;
}
this.setState({ rowState: -1 });
} else if (translationX >= LONG_SWIPE) {
toValue = 0;
if (isRTL()) {
this.hideChannel();
} else {
this.toggleRead();
}
} else if (translationX < 0 && translationX > -LONG_SWIPE) {
// open trailing option if he swipe left
if (isRTL()) {
toValue = -ACTION_WIDTH;
} else {
toValue = -2 * ACTION_WIDTH;
}
this.setState({ rowState: 1 });
} else if (translationX <= -LONG_SWIPE) {
toValue = 0;
this.setState({ rowState: 0 });
if (isRTL()) {
this.toggleRead();
} else {
this.hideChannel();
}
} else {
toValue = 0;
}
}
if (rowState === -1) {
// if left option is opened
if (this._value < SMALL_SWIPE) {
toValue = 0;
this.setState({ rowState: 0 });
} else if (this._value > LONG_SWIPE) {
toValue = 0;
this.setState({ rowState: 0 });
if (isRTL()) {
this.hideChannel();
} else {
this.toggleRead();
}
} else if (isRTL()) {
toValue = 2 * ACTION_WIDTH;
} else {
toValue = ACTION_WIDTH;
}
}
if (rowState === 1) {
// if right option is opened
if (this._value > -2 * SMALL_SWIPE) {
toValue = 0;
this.setState({ rowState: 0 });
} else if (this._value < -LONG_SWIPE) {
toValue = 0;
this.setState({ rowState: 0 });
if (isRTL()) {
this.toggleRead();
} else {
this.hideChannel();
}
} else if (isRTL()) {
toValue = -ACTION_WIDTH;
} else {
toValue = -2 * ACTION_WIDTH;
}
}
this._animateRow(toValue);
};
_animateRow = (toValue: number) => {
this.rowOffSet.setValue(this._value);
this._value = toValue;
this.dragX.setValue(0);
Animated.spring(this.rowOffSet, {
toValue,
bounciness: 0,
useNativeDriver: true
}).start();
};
close = () => {
this.setState({ rowState: 0 });
this._animateRow(0);
};
toggleFav = () => {
const { toggleFav, rid, favorite } = this.props;
if (toggleFav) { if (toggleFav) {
toggleFav(rid, favorite); toggleFav(rid, favorite);
} }
this.close(); close();
}; };
toggleRead = () => { const handleToggleRead = () => {
const { toggleRead, rid, isRead } = this.props;
if (toggleRead) { if (toggleRead) {
toggleRead(rid, isRead); toggleRead(rid, isRead);
} }
}; };
hideChannel = () => { const handleHideChannel = () => {
const { hideChannel, rid, type } = this.props;
if (hideChannel) { if (hideChannel) {
hideChannel(rid, type); hideChannel(rid, type);
} }
}; };
onToggleReadPress = () => { const onToggleReadPress = () => {
this.toggleRead(); handleToggleRead();
this.close(); close();
}; };
onHidePress = () => { const onHidePress = () => {
this.hideChannel(); handleHideChannel();
this.close(); close();
}; };
onPress = () => { const handlePress = () => {
const { rowState } = this.state; if (rowState.value !== 0) {
if (rowState !== 0) { close();
this.close();
return; return;
} }
const { onPress } = this.props;
if (onPress) { if (onPress) {
onPress(); onPress();
} }
}; };
onLongPress = () => { const handleLongPress = () => {
const { rowState } = this.state; if (rowState.value !== 0) {
const { onLongPress } = this.props; close();
if (rowState !== 0) {
this.close();
return; return;
} }
@ -210,55 +101,139 @@ class Touchable extends React.Component<ITouchableProps, any> {
} }
}; };
render() { const onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
const { testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled, displayMode } = this.props; if (nativeEvent.state === State.ACTIVE) {
handleLongPress();
}
};
return ( const handleRelease = (event: PanGestureHandlerEventPayload) => {
<LongPressGestureHandler onHandlerStateChange={this.onLongPressHandlerStateChange}> const { translationX } = event;
<Animated.View> _value += translationX;
<PanGestureHandler let toValue = 0;
minDeltaX={20} if (rowState.value === 0) {
onGestureEvent={this._onGestureEvent} // if no option is opened
onHandlerStateChange={this._onHandlerStateChange} if (translationX > 0 && translationX < LONG_SWIPE) {
enabled={swipeEnabled}> if (I18n.isRTL) {
<Animated.View> toValue = 2 * ACTION_WIDTH;
<LeftActions } else {
transX={this.transXReverse} toValue = ACTION_WIDTH;
isRead={isRead} }
width={width} rowState.value = -1;
onToggleReadPress={this.onToggleReadPress} } else if (translationX >= LONG_SWIPE) {
toValue = 0;
if (I18n.isRTL) {
handleHideChannel();
} else {
handleToggleRead();
}
} else if (translationX < 0 && translationX > -LONG_SWIPE) {
// open trailing option if he swipe left
if (I18n.isRTL) {
toValue = -ACTION_WIDTH;
} else {
toValue = -2 * ACTION_WIDTH;
}
rowState.value = 1;
} else if (translationX <= -LONG_SWIPE) {
toValue = 0;
rowState.value = 1;
if (I18n.isRTL) {
handleToggleRead();
} else {
handleHideChannel();
}
} else {
toValue = 0;
}
} else if (rowState.value === -1) {
// if left option is opened
if (_value < SMALL_SWIPE) {
toValue = 0;
rowState.value = 0;
} else if (_value > LONG_SWIPE) {
toValue = 0;
rowState.value = 0;
if (I18n.isRTL) {
handleHideChannel();
} else {
handleToggleRead();
}
} else if (I18n.isRTL) {
toValue = 2 * ACTION_WIDTH;
} else {
toValue = ACTION_WIDTH;
}
} else if (rowState.value === 1) {
// if right option is opened
if (_value > -2 * SMALL_SWIPE) {
toValue = 0;
rowState.value = 0;
} else if (_value < -LONG_SWIPE) {
if (I18n.isRTL) {
handleToggleRead();
} else {
handleHideChannel();
}
} else if (I18n.isRTL) {
toValue = -ACTION_WIDTH;
} else {
toValue = -2 * ACTION_WIDTH;
}
}
transX.value = withSpring(toValue, { overshootClamping: true });
rowOffSet.value = toValue;
_value = toValue;
};
const onGestureEvent = useAnimatedGestureHandler({
onActive: event => {
transX.value = event.translationX + rowOffSet.value;
if (transX.value > 2 * width) transX.value = 2 * width;
},
onEnd: event => {
runOnJS(handleRelease)(event);
}
});
const animatedStyles = useAnimatedStyle(() => ({ transform: [{ translateX: transX.value }] }));
return (
<LongPressGestureHandler onHandlerStateChange={onLongPressHandlerStateChange}>
<Animated.View>
<PanGestureHandler activeOffsetX={[-20, 20]} onGestureEvent={onGestureEvent} enabled={swipeEnabled}>
<Animated.View>
<LeftActions
transX={transX}
isRead={isRead}
width={width}
onToggleReadPress={onToggleReadPress}
displayMode={displayMode}
/>
<RightActions
transX={transX}
favorite={favorite}
width={width}
toggleFav={handleToggleFav}
onHidePress={onHidePress}
displayMode={displayMode}
/>
<Animated.View style={animatedStyles}>
<Touch
onPress={handlePress}
theme={theme} theme={theme}
displayMode={displayMode} testID={testID}
/>
<RightActions
transX={this.transXReverse}
favorite={favorite}
width={width}
toggleFav={this.toggleFav}
onHidePress={this.onHidePress}
theme={theme}
displayMode={displayMode}
/>
<Animated.View
style={{ style={{
transform: [{ translateX: this.transX }] backgroundColor: isFocused ? colors.chatComponentBackground : colors.backgroundColor
}}> }}>
<Touch {children}
onPress={this.onPress} </Touch>
theme={theme}
testID={testID}
style={{
backgroundColor: isFocused ? themes[theme].chatComponentBackground : themes[theme].backgroundColor
}}>
{children}
</Touch>
</Animated.View>
</Animated.View> </Animated.View>
</PanGestureHandler> </Animated.View>
</Animated.View> </PanGestureHandler>
</LongPressGestureHandler> </Animated.View>
); </LongPressGestureHandler>
} );
} };
export default Touchable; export default Touchable;

View File

@ -1,12 +1,11 @@
import React from 'react'; import React from 'react';
import { Animated } from 'react-native'; import Animated from 'react-native-reanimated';
import { TSupportedThemes } from '../../theme'; import { TSupportedThemes } from '../../theme';
import { TUserStatus, ILastMessage, SubscriptionType, IOmnichannelSource } from '../../definitions'; import { TUserStatus, ILastMessage, SubscriptionType, IOmnichannelSource } from '../../definitions';
export interface ILeftActionsProps { export interface ILeftActionsProps {
theme: TSupportedThemes; transX: Animated.SharedValue<number>;
transX: Animated.AnimatedAddition | Animated.AnimatedMultiplication;
isRead: boolean; isRead: boolean;
width: number; width: number;
onToggleReadPress(): void; onToggleReadPress(): void;
@ -14,8 +13,7 @@ export interface ILeftActionsProps {
} }
export interface IRightActionsProps { export interface IRightActionsProps {
theme: TSupportedThemes; transX: Animated.SharedValue<number>;
transX: Animated.AnimatedAddition | Animated.AnimatedMultiplication;
favorite: boolean; favorite: boolean;
width: number; width: number;
toggleFav(): void; toggleFav(): void;
@ -159,7 +157,6 @@ export interface ITouchableProps {
toggleFav: Function; toggleFav: Function;
toggleRead: Function; toggleRead: Function;
hideChannel: Function; hideChannel: Function;
theme: TSupportedThemes;
isFocused: boolean; isFocused: boolean;
swipeEnabled: boolean; swipeEnabled: boolean;
displayMode: string; displayMode: string;

View File

@ -6,7 +6,7 @@ export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
export const ROW_HEIGHT_CONDENSED = 60 * PixelRatio.getFontScale(); export const ROW_HEIGHT_CONDENSED = 60 * PixelRatio.getFontScale();
export const ACTION_WIDTH = 80; export const ACTION_WIDTH = 80;
export const SMALL_SWIPE = ACTION_WIDTH / 2; export const SMALL_SWIPE = ACTION_WIDTH / 2;
export const LONG_SWIPE = ACTION_WIDTH * 3; export const LONG_SWIPE = ACTION_WIDTH * 2.5;
export default StyleSheet.create({ export default StyleSheet.create({
flex: { flex: {

View File

@ -5,6 +5,7 @@ import { KeyCommandsEmitter } from 'react-native-keycommands';
import { initialWindowMetrics, SafeAreaProvider } from 'react-native-safe-area-context'; import { initialWindowMetrics, SafeAreaProvider } from 'react-native-safe-area-context';
import RNScreens from 'react-native-screens'; import RNScreens from 'react-native-screens';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app'; import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app';
import { deepLinkingOpen } from './actions/deepLinking'; import { deepLinkingOpen } from './actions/deepLinking';
@ -224,14 +225,16 @@ export default class Root extends React.Component<{}, IState> {
fontScale, fontScale,
setDimensions: this.setDimensions setDimensions: this.setDimensions
}}> }}>
<ActionSheetProvider> <GestureHandlerRootView style={{ flex: 1 }}>
<AppContainer /> <ActionSheetProvider>
<TwoFactor /> <AppContainer />
<ScreenLockedView /> <TwoFactor />
<ChangePasscodeView /> <ScreenLockedView />
<InAppNotification /> <ChangePasscodeView />
<Toast /> <InAppNotification />
</ActionSheetProvider> <Toast />
</ActionSheetProvider>
</GestureHandlerRootView>
</DimensionsContext.Provider> </DimensionsContext.Provider>
</ThemeContext.Provider> </ThemeContext.Provider>
</Provider> </Provider>

View File

@ -10,7 +10,7 @@ import { IUser } from '../../definitions';
import sdk from '../services/sdk'; import sdk from '../services/sdk';
import { compareServerVersion } from './helpers/compareServerVersion'; import { compareServerVersion } from './helpers/compareServerVersion';
export const _activeUsersSubTimeout: { activeUsersSubTimeout: boolean | ReturnType<typeof setTimeout> } = { export const _activeUsersSubTimeout: { activeUsersSubTimeout: boolean | ReturnType<typeof setTimeout> | number } = {
activeUsersSubTimeout: false activeUsersSubTimeout: false
}; };

View File

@ -25,7 +25,7 @@ const WINDOW_TIME = 1000;
export default class RoomSubscription { export default class RoomSubscription {
private rid: string; private rid: string;
private isAlive: boolean; private isAlive: boolean;
private timer: null | number; private timer: ReturnType<typeof setTimeout> | null;
private queue: { [key: string]: IMessage }; private queue: { [key: string]: IMessage };
private messagesBatch: {}; private messagesBatch: {};
private _messagesBatch: { [key: string]: TMessageModel }; private _messagesBatch: { [key: string]: TMessageModel };

View File

@ -41,7 +41,7 @@ const removeListener = (listener: { stop: () => void }) => listener.stop();
let streamListener: Promise<any> | false; let streamListener: Promise<any> | false;
let subServer: string; let subServer: string;
let queue: { [key: string]: ISubscription | IRoom } = {}; let queue: { [key: string]: ISubscription | IRoom } = {};
let subTimer: number | null | false = null; let subTimer: ReturnType<typeof setTimeout> | null | false = null;
const WINDOW_TIME = 500; const WINDOW_TIME = 500;
export let roomsSubscription: { stop: () => void } | null = null; export let roomsSubscription: { stop: () => void } | null = null;

View File

@ -12,6 +12,7 @@ export default function debounce(func: Function, wait?: number, immediate?: bool
}; };
const callNow = immediate && !timeout; const callNow = immediate && !timeout;
clearTimeout(timeout!); clearTimeout(timeout!);
// @ts-ignore
timeout = setTimeout(later, wait); timeout = setTimeout(later, wait);
if (callNow) { if (callNow) {
func.apply(context, args); func.apply(context, args);

View File

@ -69,7 +69,7 @@ class ListContainer extends React.Component<IListContainerProps, IListContainerS
private viewabilityConfig = { private viewabilityConfig = {
itemVisiblePercentThreshold: 10 itemVisiblePercentThreshold: 10
}; };
private highlightedMessageTimeout: number | undefined | false; private highlightedMessageTimeout: ReturnType<typeof setTimeout> | undefined | false;
private thread?: TThreadModel; private thread?: TThreadModel;
private messagesObservable?: Observable<TMessageModel[] | TThreadMessageModel[]>; private messagesObservable?: Observable<TMessageModel[] | TThreadMessageModel[]>;
private messagesSubscription?: Subscription; private messagesSubscription?: Subscription;

View File

@ -197,9 +197,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
private subSubscription?: Subscription; private subSubscription?: Subscription;
private queryUnreads?: Subscription; private queryUnreads?: Subscription;
private retryInit = 0; private retryInit = 0;
private retryInitTimeout?: number; private retryInitTimeout?: ReturnType<typeof setTimeout>;
private retryFindCount = 0; private retryFindCount = 0;
private retryFindTimeout?: number; private retryFindTimeout?: ReturnType<typeof setTimeout>;
private messageErrorActions?: IMessageErrorActions | null; private messageErrorActions?: IMessageErrorActions | null;
private messageActions?: IMessageActions | null; private messageActions?: IMessageActions | null;
// Type of InteractionManager.runAfterInteractions // Type of InteractionManager.runAfterInteractions

View File

@ -536,7 +536,7 @@ PODS:
- RNFBApp - RNFBApp
- RNFileViewer (2.1.5): - RNFileViewer (2.1.5):
- React-Core - React-Core
- RNGestureHandler (1.10.3): - RNGestureHandler (2.4.2):
- React-Core - React-Core
- RNImageCropPicker (0.36.3): - RNImageCropPicker (0.36.3):
- React-Core - React-Core
@ -961,7 +961,7 @@ SPEC CHECKSUMS:
EXVideoThumbnails: 442c3abadb51a81551a3b53705b7560de390e6f7 EXVideoThumbnails: 442c3abadb51a81551a3b53705b7560de390e6f7
EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5 EXWebBrowser: 76783ba5dcb8699237746ecf41a9643d428a4cc5
FBLazyVector: c9b6dfcde9b3d497793c40d4ccbfbfb05092e0df FBLazyVector: c9b6dfcde9b3d497793c40d4ccbfbfb05092e0df
FBReactNativeSpec: addc4f0e6ab00dc628fe91de8bfca4601762673a FBReactNativeSpec: c39f7fc0cd6cc64f0a2a5beffc64b1aa5d42740e
Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892 Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892
FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4 FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4
FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085 FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085
@ -1046,7 +1046,7 @@ SPEC CHECKSUMS:
RNFBApp: 6fd8a7e757135d4168bf033a8812c241af7363a0 RNFBApp: 6fd8a7e757135d4168bf033a8812c241af7363a0
RNFBCrashlytics: 88de72c2476b5868a892d9523b89b86c527c540e RNFBCrashlytics: 88de72c2476b5868a892d9523b89b86c527c540e
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592 RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211 RNGestureHandler: 61628a2c859172551aa2100d3e73d1e57878392f
RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
RNReanimated: 241c586663f44f19a53883c63375fdd041253960 RNReanimated: 241c586663f44f19a53883c63375fdd041253960

View File

@ -1870,7 +1870,7 @@
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386"; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@ -1925,7 +1925,7 @@
COPY_PHASE_STRIP = YES; COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386"; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@ -1,5 +1,7 @@
import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock.js'; import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock.js';
require('react-native-reanimated/lib/reanimated2/jestUtils').setUpTests();
jest.mock('@react-native-clipboard/clipboard', () => mockClipboard); jest.mock('@react-native-clipboard/clipboard', () => mockClipboard);
jest.mock('react-native-mmkv-storage', () => ({ jest.mock('react-native-mmkv-storage', () => ({
@ -30,4 +32,6 @@ jest.mock('react-native-file-viewer', () => ({
open: jest.fn(() => null) open: jest.fn(() => null)
})); }));
jest.mock('expo-haptics', () => jest.fn(() => null));
jest.mock('./app/lib/database', () => jest.fn(() => null)); jest.mock('./app/lib/database', () => jest.fn(() => null));

View File

@ -90,8 +90,8 @@
"react-native-easy-grid": "^0.2.2", "react-native-easy-grid": "^0.2.2",
"react-native-easy-toast": "^1.2.0", "react-native-easy-toast": "^1.2.0",
"react-native-fast-image": "RocketChat/react-native-fast-image.git#bump-version", "react-native-fast-image": "RocketChat/react-native-fast-image.git#bump-version",
"react-native-file-viewer": "^2.1.5", "react-native-file-viewer": "^2.1.4",
"react-native-gesture-handler": "^1.10.3", "react-native-gesture-handler": "2.4.2",
"react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker", "react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker",
"react-native-image-progress": "^1.1.1", "react-native-image-progress": "^1.1.1",
"react-native-jitsi-meet": "RocketChat/react-native-jitsi-meet", "react-native-jitsi-meet": "RocketChat/react-native-jitsi-meet",

View File

@ -3,6 +3,7 @@ import React from 'react';
import { Dimensions, ScrollView } from 'react-native'; import { Dimensions, ScrollView } from 'react-native';
import { storiesOf } from '@storybook/react-native'; import { storiesOf } from '@storybook/react-native';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import RoomItemComponent from '../../app/containers/RoomItem/RoomItem'; import RoomItemComponent from '../../app/containers/RoomItem/RoomItem';
import { longText } from '../utils'; import { longText } from '../utils';
@ -39,6 +40,7 @@ const RoomItem = props => (
const stories = storiesOf('Room Item', module) const stories = storiesOf('Room Item', module)
.addDecorator(story => <Provider store={store}>{story()}</Provider>) .addDecorator(story => <Provider store={store}>{story()}</Provider>)
.addDecorator(story => <SafeAreaProvider>{story()}</SafeAreaProvider>)
.addDecorator(story => <ScrollView style={{ backgroundColor: themes[_theme].backgroundColor }}>{story()}</ScrollView>); .addDecorator(story => <ScrollView style={{ backgroundColor: themes[_theme].backgroundColor }}>{story()}</ScrollView>);
stories.add('Basic', () => <RoomItem />); stories.add('Basic', () => <RoomItem />);

File diff suppressed because one or more lines are too long

View File

@ -4525,9 +4525,9 @@
"@types/node" "*" "@types/node" "*"
"@types/hammerjs@^2.0.36": "@types/hammerjs@^2.0.36":
version "2.0.36" version "2.0.41"
resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.36.tgz#17ce0a235e9ffbcdcdf5095646b374c2bf615a4c" resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.41.tgz#f6ecf57d1b12d2befcce00e928a6a097c22980aa"
integrity sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ== integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==
"@types/history@*": "@types/history@*":
version "4.7.6" version "4.7.6"
@ -12398,7 +12398,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.7.0: lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -14963,7 +14963,7 @@ react-native-fast-image@RocketChat/react-native-fast-image.git#bump-version:
version "8.5.12" version "8.5.12"
resolved "https://codeload.github.com/RocketChat/react-native-fast-image/tar.gz/8bdb187a4500e23ad1c95324a669c25361bbf685" resolved "https://codeload.github.com/RocketChat/react-native-fast-image/tar.gz/8bdb187a4500e23ad1c95324a669c25361bbf685"
react-native-file-viewer@^2.1.5: react-native-file-viewer@^2.1.4:
version "2.1.5" version "2.1.5"
resolved "https://registry.yarnpkg.com/react-native-file-viewer/-/react-native-file-viewer-2.1.5.tgz#cd4544f573108e79002b5c7e1ebfce4371885250" resolved "https://registry.yarnpkg.com/react-native-file-viewer/-/react-native-file-viewer-2.1.5.tgz#cd4544f573108e79002b5c7e1ebfce4371885250"
integrity sha512-MGC6sx9jsqHdefhVQ6o0akdsPGpkXgiIbpygb2Sg4g4bh7v6K1cardLV1NwGB9A6u1yICOSDT/MOC//9Ez6EUg== integrity sha512-MGC6sx9jsqHdefhVQ6o0akdsPGpkXgiIbpygb2Sg4g4bh7v6K1cardLV1NwGB9A6u1yICOSDT/MOC//9Ez6EUg==
@ -14973,15 +14973,15 @@ react-native-flipper@^0.34.0:
resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d" resolved "https://registry.yarnpkg.com/react-native-flipper/-/react-native-flipper-0.34.0.tgz#7df1f38ba5d97a9321125fe0fccbe47d99e6fa1d"
integrity sha512-48wgm29HJTOlZ0DibBsvXueEOY0EPIVL0wWKbwRfgrk86+luSEuLW3aZC50oJa95zSFb9qYShTV/6dWqh4Jamg== integrity sha512-48wgm29HJTOlZ0DibBsvXueEOY0EPIVL0wWKbwRfgrk86+luSEuLW3aZC50oJa95zSFb9qYShTV/6dWqh4Jamg==
react-native-gesture-handler@^1.10.3: react-native-gesture-handler@2.4.2:
version "1.10.3" version "2.4.2"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz#942bbf2963bbf49fa79593600ee9d7b5dab3cfc0" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.4.2.tgz#de93760b0bc251d94e8ae692f9850ec3ed2e4f27"
integrity sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw== integrity sha512-K3oMiQV7NOVB5RvNlxkyJxU1Gn6m1cYu53MoFA542FVDSTR491d1eQkWDdqy4lW52rfF7IK7eE1LCi+kTJx7jw==
dependencies: dependencies:
"@egjs/hammerjs" "^2.0.17" "@egjs/hammerjs" "^2.0.17"
fbjs "^3.0.0"
hoist-non-react-statics "^3.3.0" hoist-non-react-statics "^3.3.0"
invariant "^2.2.4" invariant "^2.2.4"
lodash "^4.17.21"
prop-types "^15.7.2" prop-types "^15.7.2"
react-native-image-crop-picker@RocketChat/react-native-image-crop-picker: react-native-image-crop-picker@RocketChat/react-native-image-crop-picker: