diff --git a/NOTICE.txt b/NOTICE.txt
index dec80e254..4fbbd80c4 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1466,6 +1466,41 @@ SOFTWARE.
---
+## react-native-gesture-handler
+
+This product contains 'react-native-gesture-handler' by kmagiera
+
+Declarative API exposing platform native touch and gesture system to React Native.
+
+* HOMEPAGE:
+ * https://github.com/kmagiera/react-native-gesture-handler
+
+* LICENSE: MIT
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Krzysztof Magiera
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+---
+
## react-native-image-gallery
This product contains a modified version of 'react-native-image-gallery' by Archriss.
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 26ff70a8c..6a0484b8c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -216,6 +216,7 @@ dependencies {
implementation project(':rn-fetch-blob')
implementation project(':react-native-recyclerview-list')
implementation project(':react-native-webview')
+ implementation project(':react-native-gesture-handler')
// For animated GIF support
implementation 'com.facebook.fresco:fresco:1.10.0'
diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
index d1cb92490..665001ccc 100644
--- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
+++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java
@@ -24,6 +24,7 @@ import com.gantix.JailMonkey.JailMonkeyPackage;
import io.tradle.react.LocalAuthPackage;
import com.github.godness84.RNRecyclerViewList.RNRecyclerviewListPackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
+import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
@@ -84,7 +85,8 @@ public class MainApplication extends NavigationApplication implements INotificat
new KeychainPackage(),
new InitializationPackage(this),
new RNRecyclerviewListPackage(),
- new RNCWebViewPackage()
+ new RNCWebViewPackage(),
+ new RNGestureHandlerPackage()
);
}
diff --git a/android/settings.gradle b/android/settings.gradle
index 54aeafb59..b3afe985e 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -1,4 +1,6 @@
rootProject.name = 'Mattermost'
+include ':react-native-gesture-handler'
+project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
include ':react-native-document-picker'
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-document-picker/android')
include ':react-native-keychain'
diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js
index 12949ef87..86c5f14a6 100644
--- a/app/actions/views/channel.js
+++ b/app/actions/views/channel.js
@@ -228,8 +228,8 @@ export function loadFilesForPostIfNecessary(postId) {
const {files} = getState().entities;
const fileIdsForPost = files.fileIdsByPostId[postId];
- if (!fileIdsForPost) {
- await getFilesForPost(postId)(dispatch, getState);
+ if (!fileIdsForPost?.length) {
+ await dispatch(getFilesForPost(postId));
}
};
}
diff --git a/app/components/post/post.js b/app/components/post/post.js
index 018783fdc..e63028afc 100644
--- a/app/components/post/post.js
+++ b/app/components/post/post.js
@@ -268,7 +268,7 @@ export default class Post extends PureComponent {
const highlightFlagged = isFlagged && !skipFlaggedHeader;
const hightlightPinned = post.is_pinned && !skipPinnedHeader;
- let highlighted = highlight ? style.highlight : null;
+ let highlighted;
if (highlight) {
highlighted = style.highlight;
} else if ((highlightFlagged || hightlightPinned) && highlightPinnedOrFlagged) {
diff --git a/app/components/post_body/post_body.js b/app/components/post_body/post_body.js
index d16f529c3..9eae3e12f 100644
--- a/app/components/post_body/post_body.js
+++ b/app/components/post_body/post_body.js
@@ -164,6 +164,7 @@ export default class PostBody extends PureComponent {
backButtonTitle: '',
navigatorStyle: {
navBarHidden: true,
+ navBarTransparent: true,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
diff --git a/app/components/reactions/reactions.js b/app/components/reactions/reactions.js
index e29f83908..aaaa87bae 100644
--- a/app/components/reactions/reactions.js
+++ b/app/components/reactions/reactions.js
@@ -95,6 +95,7 @@ export default class Reactions extends PureComponent {
backButtonTitle: '',
navigatorStyle: {
navBarHidden: true,
+ navBarTransparent: true,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
diff --git a/app/components/slide_up_panel/slide_up_panel.js b/app/components/slide_up_panel/slide_up_panel.js
index 270a028ac..90e65ad61 100644
--- a/app/components/slide_up_panel/slide_up_panel.js
+++ b/app/components/slide_up_panel/slide_up_panel.js
@@ -3,13 +3,13 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
+import {Animated, Platform, StyleSheet, View} from 'react-native';
import {
- Animated,
- PanResponder,
- Platform,
- StyleSheet,
- View,
-} from 'react-native';
+ PanGestureHandler,
+ NativeViewGestureHandler,
+ State as GestureState,
+ TapGestureHandler,
+} from 'react-native-gesture-handler';
import {DeviceTypes} from 'app/constants';
@@ -19,25 +19,33 @@ export const BOTTOM_MARGIN = DeviceTypes.IS_IPHONE_X ? 24 : 0;
const TOP_IOS_MARGIN = DeviceTypes.IS_IPHONE_X ? 84 : 64;
const TOP_ANDROID_MARGIN = 44;
const TOP_MARGIN = Platform.OS === 'ios' ? TOP_IOS_MARGIN : TOP_ANDROID_MARGIN;
-const CONTAINER_MARGIN = TOP_MARGIN - 10;
export default class SlideUpPanel extends PureComponent {
static propTypes = {
+
+ // Whether or not to allow the panel to snap to the initial position after it has been opened
allowStayMiddle: PropTypes.bool,
- alwaysCaptureContainerMove: PropTypes.bool,
+
containerHeight: PropTypes.number,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
+ header: PropTypes.func,
headerHeight: PropTypes.number,
+
+ // The initial position of the SlideUpPanel when it's first opened. If this value is between 0 and 1,
+ // it is treated as a percentage of the containerHeight.
initialPosition: PropTypes.number,
+
+ // The space between the top of the panel and the top of the container when the SlideUpPanel is fully open.
marginFromTop: PropTypes.number,
onRequestClose: PropTypes.func,
};
static defaultProps = {
allowStayMiddle: true,
+ header: () => null,
headerHeight: 0,
initialPosition: 0.5,
marginFromTop: TOP_MARGIN,
@@ -47,189 +55,244 @@ export default class SlideUpPanel extends PureComponent {
constructor(props) {
super(props);
+ const {containerHeight, headerHeight, marginFromTop} = props;
+
+ this.masterRef = React.createRef();
+ this.panRef = React.createRef();
+ this.scrollRef = React.createRef();
+ this.scrollViewRef = React.createRef();
+ this.headerRef = React.createRef();
+ this.backdropRef = React.createRef();
+
const initialUsedSpace = Math.abs(props.initialPosition);
let initialPosition;
if (initialUsedSpace <= 1) {
- initialPosition = ((props.containerHeight - (props.headerHeight + BOTTOM_MARGIN)) * (1 - initialUsedSpace));
+ initialPosition = ((containerHeight - (headerHeight + BOTTOM_MARGIN)) * (1 - initialUsedSpace));
} else {
- initialPosition = ((props.containerHeight - (props.headerHeight + BOTTOM_MARGIN)) - initialUsedSpace);
+ initialPosition = ((containerHeight - (headerHeight + BOTTOM_MARGIN)) - initialUsedSpace);
}
- this.mainPanGesture = PanResponder.create({
- onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
- if (this.props.alwaysCaptureContainerMove) {
- return gestureState.dy !== 0;
- }
- const isGoingDown = gestureState.y0 < gestureState.dy;
- return this.isAValidMovement(gestureState.dx, gestureState.dy, isGoingDown);
- },
- onPanResponderMove: (evt, gestureState) => {
- const isGoingDown = gestureState.dy > 0;
- if (this.props.alwaysCaptureContainerMove &&
- !this.isAValidMovement(gestureState.dx, gestureState.dy, isGoingDown)) {
- return;
- }
-
- this.moveStart(gestureState);
- },
- onPanResponderRelease: (evt, gestureState) => {
- this.moveFinished(gestureState);
- },
- });
-
- this.secondaryPanGesture = PanResponder.create({
- onMoveShouldSetPanResponder: (evt, gestureState) => {
- const isGoingDown = gestureState.y0 < gestureState.dy;
- return this.isAValidMovement(gestureState.dx, gestureState.dy, isGoingDown, true);
- },
- onPanResponderMove: (evt, gestureState) => {
- this.moveStart(gestureState);
- },
- onPanResponderRelease: (evt, gestureState) => {
- this.moveFinished(gestureState);
- },
- });
-
- this.previousTop = initialPosition;
- this.canDrag = true;
+ // These values correspond to when the panel is fully open, when it is initially opened, and when it is closed
+ this.snapPoints = [marginFromTop, initialPosition, containerHeight];
this.state = {
- position: new Animated.Value(props.containerHeight),
- initialPosition,
- finalPosition: props.marginFromTop,
- endPosition: 0,
+ lastSnap: initialPosition,
};
+
+ this.lastScrollYValue = 0;
+ this.lastScrollY = new Animated.Value(0);
+ this.onRegisterLastScroll = Animated.event(
+ [{
+ nativeEvent: {
+ contentOffset: {
+ y: this.lastScrollY,
+ },
+ },
+ }],
+ {useNativeDriver: true},
+ );
+ this.lastScrollY.addListener(({value}) => {
+ this.lastScrollYValue = value;
+ });
+
+ this.dragY = new Animated.Value(0);
+ this.onGestureEvent = Animated.event(
+ [{
+ nativeEvent: {
+ translationY: this.dragY,
+ },
+ }],
+ {useNativeDriver: true},
+ );
+
+ this.reverseLastScrollY = Animated.multiply(
+ new Animated.Value(-1),
+ this.lastScrollY
+ );
+
+ this.translateYOffset = new Animated.Value(containerHeight);
+ this.translateY = Animated.add(
+ this.translateYOffset,
+ Animated.add(this.dragY, this.reverseLastScrollY)
+ ).interpolate({
+ inputRange: [marginFromTop, containerHeight],
+ outputRange: [marginFromTop, containerHeight],
+ extrapolate: 'clamp',
+ });
}
componentDidMount() {
- this.startAnimation(this.props.containerHeight, this.state.initialPosition, false, true);
+ Animated.timing(this.translateYOffset, {
+ duration: 200,
+ toValue: this.snapPoints[1],
+ useNativeDriver: true,
+ }).start();
}
- handleTouchEnd = () => {
- if (!this.isDragging) {
- this.startAnimation(this.state.endPosition, this.props.containerHeight, false, true);
- }
- };
-
- isAValidMovement = (distanceX, distanceY, isGoingDown, forceCheck = false) => {
- const {endPosition, finalPosition} = this.state;
-
- if (finalPosition !== endPosition || forceCheck || (isGoingDown && this.canDrag)) {
- const moveTravelledFarEnough = Math.abs(distanceY) > Math.abs(distanceX) && Math.abs(distanceY) > 2;
- return moveTravelledFarEnough;
- }
-
- return false;
- };
-
- moveStart = (gestureState) => {
- if (this.viewRef && this.backdrop) {
- const {endPosition} = this.state;
- const position = endPosition - (gestureState.y0 - gestureState.moveY);
- this.isDragging = true;
-
- this.backdrop.setNativeProps({pointerEvents: 'none'});
- this.updatePosition(position);
- }
- };
-
- moveFinished = (gestureState) => {
- if (this.viewRef) {
- const isGoingDown = gestureState.y0 < gestureState.moveY;
- let position = gestureState.moveY;
- if (this.previousTop !== position) {
- position = this.previousTop;
- }
-
- this.startAnimation(gestureState.y0, position, isGoingDown);
- }
- };
-
- setBackdropRef = (ref) => {
- this.backdrop = ref;
- };
-
- setDrag = (val) => {
- this.canDrag = val;
- };
-
- setViewRef = (ref) => {
- this.viewRef = ref;
- };
-
- startAnimation = (initialY, positionY, isGoingDown, initial = false) => {
- const {allowStayMiddle, containerHeight, onRequestClose} = this.props;
- const {finalPosition, initialPosition} = this.state;
- const position = new Animated.Value(initial ? initialY : positionY);
- let endPosition = (!isGoingDown && !initial ? finalPosition : positionY);
-
- position.removeAllListeners();
- if (isGoingDown) {
- if (positionY <= this.state.initialPosition && allowStayMiddle) {
- endPosition = initialPosition;
- } else {
- endPosition = containerHeight;
- }
- }
-
- Animated.timing(position, {
- toValue: endPosition,
- duration: initial ? 200 : 100,
+ closeWithAnimation = () => {
+ Animated.timing(this.translateYOffset, {
+ duration: 200,
+ toValue: this.snapPoints[2],
useNativeDriver: true,
- }).start(() => {
- if (this.viewRef && this.backdrop) {
- this.setState({endPosition});
- this.backdrop.setNativeProps({pointerEvents: 'box-only'});
- this.isDragging = false;
-
- if (endPosition === containerHeight) {
- onRequestClose();
- }
- }
- });
-
- position.addListener((pos) => {
- if (this.viewRef) {
- this.updatePosition(pos.value);
- }
- });
+ }).start(() => this.props.onRequestClose());
};
- updatePosition = (newPosition) => {
- const {position} = this.state;
- this.previousTop = newPosition;
- position.setValue(newPosition);
+ onHeaderHandlerStateChange = ({nativeEvent}) => {
+ if (nativeEvent.oldState === GestureState.BEGAN) {
+ this.lastScrollY.setValue(0);
+ }
+ this.onHandlerStateChange({nativeEvent});
+ };
+
+ onHandlerStateChange = ({nativeEvent}) => {
+ if (nativeEvent.oldState === GestureState.ACTIVE) {
+ const {translationY, velocityY} = nativeEvent;
+ const {allowStayMiddle} = this.props;
+ const {lastSnap} = this.state;
+ const isGoingDown = translationY > 0;
+ const translation = translationY - this.lastScrollYValue;
+
+ const endOffsetY = lastSnap + translation;
+ let destSnapPoint = this.snapPoints[0];
+
+ if (Math.abs(translationY) < 50) {
+ // Only drag the panel after moving 50 or more points
+ destSnapPoint = lastSnap;
+ } else if (isGoingDown && !allowStayMiddle) {
+ // Just close the panel if the user pans down and we can't snap to the middle
+ destSnapPoint = this.snapPoints[2];
+ } else if (isGoingDown) {
+ destSnapPoint = this.snapPoints.find((s) => s >= endOffsetY);
+ } else {
+ destSnapPoint = this.snapPoints.find((s) => s <= endOffsetY);
+ }
+
+ if (destSnapPoint) {
+ this.translateYOffset.extractOffset();
+ this.translateYOffset.setValue(translationY);
+ this.translateYOffset.flattenOffset();
+ this.dragY.setValue(0);
+
+ if (destSnapPoint === this.snapPoints[2]) {
+ this.closeWithAnimation();
+ } else {
+ Animated.spring(this.translateYOffset, {
+ velocity: velocityY,
+ tension: 68,
+ friction: 12,
+ toValue: destSnapPoint,
+ useNativeDriver: true,
+ }).start(() => {
+ this.setState({lastSnap: destSnapPoint});
+ });
+ }
+ } else {
+ Animated.spring(this.translateYOffset, {
+ velocity: velocityY,
+ tension: 68,
+ friction: 12,
+ toValue: lastSnap,
+ useNativeDriver: true,
+ }).start();
+ }
+ }
+ };
+
+ onSingleTap = ({nativeEvent}) => {
+ if (nativeEvent.state === GestureState.ACTIVE) {
+ this.closeWithAnimation();
+ }
+ };
+
+ scrollToTop = () => {
+ if (this.scrollViewRef?.current) {
+ this.scrollViewRef.current._component.scrollTo({ //eslint-disable-line no-underscore-dangle
+ x: 0,
+ y: 0,
+ animated: false,
+ });
+ }
};
render() {
- const {children} = this.props;
- const containerPosition = {
- top: this.state.position,
+ const {children, header} = this.props;
+ const {lastSnap} = this.state;
+ const translateStyle = {
+ transform: [{translateY: this.translateY}],
};
return (
-
+
-
-
-
- {children}
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {header(this.headerRef)}
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
);
}
}
@@ -241,6 +304,8 @@ const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
+ },
+ border: {
...Platform.select({
android: {
borderTopRightRadius: 2,
diff --git a/app/components/slide_up_panel/slide_up_panel_indicator.js b/app/components/slide_up_panel/slide_up_panel_indicator.js
index 53b3cb425..e50d96162 100644
--- a/app/components/slide_up_panel/slide_up_panel_indicator.js
+++ b/app/components/slide_up_panel/slide_up_panel_indicator.js
@@ -1,32 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
+import React from 'react';
import {Animated, Platform, StyleSheet, View} from 'react-native';
-export default class SlideUpPanelIndicator extends PureComponent {
- static propTypes = {
- containerPosition: PropTypes.object.isRequired,
- panHandlers: PropTypes.object.isRequired,
- };
-
- render() {
- const {panHandlers, containerPosition} = this.props;
-
- if (Platform.OS === 'android') {
- return null;
- }
-
- return (
-
-
-
- );
+export default function slideUpPanelIndicator() {
+ if (Platform.OS === 'android') {
+ return null;
}
+
+ return (
+
+
+
+ );
}
const styles = StyleSheet.create({
diff --git a/app/screens/index.js b/app/screens/index.js
index 92b2ef1e0..b2cfa0072 100644
--- a/app/screens/index.js
+++ b/app/screens/index.js
@@ -2,6 +2,7 @@
// See LICENSE.txt for license information.
import {Navigation} from 'react-native-navigation';
+import {gestureHandlerRootHOC} from 'react-native-gesture-handler';
import Channel from 'app/screens/channel';
import Entry from 'app/screens/entry';
@@ -47,8 +48,8 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(require('app/screens/options_modal').default), store, Provider);
Navigation.registerComponent('Permalink', () => wrapWithContextProvider(require('app/screens/permalink').default), store, Provider);
Navigation.registerComponent('PinnedPosts', () => wrapWithContextProvider(require('app/screens/pinned_posts').default), store, Provider);
- Navigation.registerComponent('PostOptions', () => wrapWithContextProvider(require('app/screens/post_options').default), store, Provider);
- Navigation.registerComponent('ReactionList', () => wrapWithContextProvider(require('app/screens/reaction_list').default), store, Provider);
+ Navigation.registerComponent('PostOptions', () => gestureHandlerRootHOC(wrapWithContextProvider(require('app/screens/post_options').default)), store, Provider);
+ Navigation.registerComponent('ReactionList', () => gestureHandlerRootHOC(wrapWithContextProvider(require('app/screens/reaction_list').default)), store, Provider);
Navigation.registerComponent('RecentMentions', () => wrapWithContextProvider(require('app/screens/recent_mentions').default), store, Provider);
Navigation.registerComponent('Root', () => require('app/screens/root').default, store, Provider);
Navigation.registerComponent('Search', () => wrapWithContextProvider(require('app/screens/search').default), store, Provider);
diff --git a/app/screens/post_options/post_option.js b/app/screens/post_options/post_option.js
index 30fc70163..43c937efc 100644
--- a/app/screens/post_options/post_option.js
+++ b/app/screens/post_options/post_option.js
@@ -39,8 +39,15 @@ export default class PostOption extends PureComponent {
text: PropTypes.string.isRequired,
};
+ handleOnPress = () => {
+ // Wait for the tap animation so that the user has some feedback
+ setTimeout(() => {
+ this.props.onPress();
+ }, 250);
+ };
+
render() {
- const {destructive, icon, onPress, text} = this.props;
+ const {destructive, icon, text} = this.props;
const image = icons[icon];
const Touchable = Platform.select({
@@ -50,12 +57,12 @@ export default class PostOption extends PureComponent {
const touchableProps = Platform.select({
ios: {
- underlayColor: 'rgba(0, 0, 0, 0.05)',
+ underlayColor: 'rgba(0, 0, 0, 0.1)',
},
android: {
background: TouchableNativeFeedback.Ripple( //eslint-disable-line new-cap
- 'rgba(0, 0, 0, 0.05)',
- true,
+ 'rgba(0, 0, 0, 0.1)',
+ false,
),
},
});
@@ -63,8 +70,9 @@ export default class PostOption extends PureComponent {
return (
diff --git a/app/screens/post_options/post_options.js b/app/screens/post_options/post_options.js
index ff091e4bd..31f576e69 100644
--- a/app/screens/post_options/post_options.js
+++ b/app/screens/post_options/post_options.js
@@ -55,7 +55,7 @@ export default class PostOptions extends PureComponent {
closeWithAnimation = () => {
if (this.slideUpPanel) {
- this.slideUpPanel.getWrappedInstance().handleTouchEnd();
+ this.slideUpPanel.getWrappedInstance().closeWithAnimation();
} else {
this.close();
}
@@ -386,18 +386,16 @@ export default class PostOptions extends PureComponent {
render() {
const {deviceHeight} = this.props;
const options = this.getPostOptions();
- const initialPosition = (options.length + 1) * OPTION_HEIGHT;
const marginFromTop = deviceHeight - BOTTOM_MARGIN - ((options.length + 1) * OPTION_HEIGHT);
return (
-
+
{options}
@@ -407,7 +405,7 @@ export default class PostOptions extends PureComponent {
}
const style = StyleSheet.create({
- flex: {
+ container: {
flex: 1,
},
});
diff --git a/app/screens/reaction_list/__snapshots__/reaction_header.test.js.snap b/app/screens/reaction_list/__snapshots__/reaction_header.test.js.snap
index a78c499fa..c99530a23 100644
--- a/app/screens/reaction_list/__snapshots__/reaction_header.test.js.snap
+++ b/app/screens/reaction_list/__snapshots__/reaction_header.test.js.snap
@@ -1,92 +1,94 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReactionHeader should match snapshot 1`] = `
-
+
-
-
+
-
+
-
-
+ />
+
+
+
`;
exports[`ReactionHeader should match snapshot, renderContent 1`] = `
diff --git a/app/screens/reaction_list/__snapshots__/reaction_list.test.js.snap b/app/screens/reaction_list/__snapshots__/reaction_list.test.js.snap
index a54ade334..b24e7b8e7 100644
--- a/app/screens/reaction_list/__snapshots__/reaction_list.test.js.snap
+++ b/app/screens/reaction_list/__snapshots__/reaction_list.test.js.snap
@@ -9,231 +9,155 @@ exports[`ReactionList should match snapshot 1`] = `
}
>
-
+
+
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+ }
+ teammateNameDisplay="username"
+ theme={
+ Object {
+ "awayIndicator": "#ffbc42",
+ "buttonBg": "#166de0",
+ "buttonColor": "#ffffff",
+ "centerChannelBg": "#ffffff",
+ "centerChannelColor": "#3d3c40",
+ "codeTheme": "github",
+ "dndIndicator": "#f74343",
+ "errorTextColor": "#fd5960",
+ "linkColor": "#2389d7",
+ "mentionBj": "#ffffff",
+ "mentionColor": "#145dbf",
+ "mentionHighlightBg": "#ffe577",
+ "mentionHighlightLink": "#166de0",
+ "newMessageSeparator": "#ff8800",
+ "onlineIndicator": "#06d6a0",
+ "sidebarBg": "#145dbf",
+ "sidebarHeaderBg": "#1153ab",
+ "sidebarHeaderTextColor": "#ffffff",
+ "sidebarText": "#ffffff",
+ "sidebarTextActiveBorder": "#579eff",
+ "sidebarTextActiveColor": "#ffffff",
+ "sidebarTextHoverBg": "#4578bf",
+ "sidebarUnreadText": "#ffffff",
+ "type": "Mattermost",
+ }
+ }
+ user={
+ Object {
+ "id": "user_id_1",
+ "username": "username_1",
+ }
+ }
+ />
+
+
`;
diff --git a/app/screens/reaction_list/reaction_header.js b/app/screens/reaction_list/reaction_header.js
index 988cc9f31..af25802a4 100644
--- a/app/screens/reaction_list/reaction_header.js
+++ b/app/screens/reaction_list/reaction_header.js
@@ -4,16 +4,18 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
+ Animated,
Platform,
ScrollView,
StyleSheet,
- View,
} from 'react-native';
+import {NativeViewGestureHandler} from 'react-native-gesture-handler';
import ReactionHeaderItem from './reaction_header_item';
export default class ReactionHeader extends PureComponent {
static propTypes = {
+ forwardedRef: PropTypes.object,
selected: PropTypes.string.isRequired,
onSelectReaction: PropTypes.func.isRequired,
reactions: PropTypes.array.isRequired,
@@ -41,15 +43,19 @@ export default class ReactionHeader extends PureComponent {
render() {
return (
-
-
- {this.renderReactionHeaderItems()}
-
-
+
+
+
+ {this.renderReactionHeaderItems()}
+
+
+
);
}
}
diff --git a/app/screens/reaction_list/reaction_list.js b/app/screens/reaction_list/reaction_list.js
index af226b5e1..259f78bb4 100644
--- a/app/screens/reaction_list/reaction_list.js
+++ b/app/screens/reaction_list/reaction_list.js
@@ -3,7 +3,8 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {ScrollView, View} from 'react-native';
+import {View} from 'react-native';
+
import {intlShape} from 'react-intl';
import SlideUpPanel from 'app/components/slide_up_panel';
@@ -41,10 +42,8 @@ export default class ReactionList extends PureComponent {
constructor(props) {
super(props);
const {reactions, userProfiles} = props;
-
const reactionsByName = getReactionsByName(reactions);
- this.contentOffsetY = -1;
this.state = {
allUserIds: getUniqueUserIds(reactions),
reactions,
@@ -123,25 +122,9 @@ export default class ReactionList extends PureComponent {
handleOnSelectReaction = (emoji) => {
this.setState({selected: emoji});
- const slide = this.slideUpPanel?.getWrappedInstance();
- if (slide) {
- slide.setDrag(true);
- }
-
- if (this.scrollView) {
- this.scrollView.scrollTo({x: 0, y: 0, animated: false});
- }
- };
-
- handleScroll = (e) => {
- const pageOffsetY = e.nativeEvent.contentOffset.y;
- const canDrag = pageOffsetY <= 0;
- const slide = this.slideUpPanel?.getWrappedInstance();
-
- this.contentOffsetY = pageOffsetY;
- if (slide) {
- slide.setDrag(canDrag);
+ if (this.slideUpPanel) {
+ this.slideUpPanel.getWrappedInstance().scrollToTop();
}
};
@@ -149,10 +132,6 @@ export default class ReactionList extends PureComponent {
this.slideUpPanel = r;
};
- refScrollView = (ref) => {
- this.scrollView = ref;
- };
-
renderReactionRows = () => {
const {
navigator,
@@ -185,15 +164,23 @@ export default class ReactionList extends PureComponent {
));
};
+ renderHeader = (forwardedRef) => {
+ const {theme} = this.props;
+ const {selected, sortedReactionsForHeader} = this.state;
+
+ return (
+
+ );
+ };
+
render() {
- const {
- theme,
- } = this.props;
- const {
- selected,
- sortedReactionsForHeader,
- } = this.state;
- const style = getStyleSheet(theme);
+ const style = getStyleSheet(this.props.theme);
return (
@@ -201,25 +188,10 @@ export default class ReactionList extends PureComponent {
ref={this.refSlideUpPanel}
onRequestClose={this.close}
initialPosition={0.55}
+ header={this.renderHeader}
headerHeight={37.5}
>
-
-
-
-
-
- {this.renderReactionRows()}
-
-
+ {this.renderReactionRows()}
);
@@ -233,8 +205,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
},
headerContainer: {
height: 37.5,
- borderColor: changeOpacity(theme.centerChannelColor, 0.2),
- borderBottomWidth: 1,
},
rowContainer: {
justifyContent: 'center',
diff --git a/app/selectors/post_list.js b/app/selectors/post_list.js
index 1cb608043..38c996cf9 100644
--- a/app/selectors/post_list.js
+++ b/app/selectors/post_list.js
@@ -58,9 +58,10 @@ export function makePreparePostIdsForPostList() {
// Push on a date header if the last post was on a different day than the current one
const postDate = new Date(post.create_at);
+ postDate.setHours(0, 0, 0, 0);
if (!lastDate || lastDate.toDateString() !== postDate.toDateString()) {
- out.push(DATE_LINE + post.create_at);
+ out.push(DATE_LINE + postDate.getTime());
lastDate = postDate;
}
diff --git a/app/selectors/post_list.test.js b/app/selectors/post_list.test.js
index d69965e00..bad10549b 100644
--- a/app/selectors/post_list.test.js
+++ b/app/selectors/post_list.test.js
@@ -18,13 +18,16 @@ describe('Selectors.PostList', () => {
describe('makePreparePostIdsForPostList', () => {
it('filter join/leave posts', () => {
const preparePostIdsForPostList = makePreparePostIdsForPostList();
+ const time = Date.now();
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
let state = {
entities: {
posts: {
posts: {
- 1001: {id: '1001', create_at: 0, type: ''},
- 1002: {id: '1002', create_at: 1, type: Posts.POST_TYPES.JOIN_CHANNEL},
+ 1001: {id: '1001', create_at: time, type: ''},
+ 1002: {id: '1002', create_at: time + 1, type: Posts.POST_TYPES.JOIN_CHANNEL},
},
},
preferences: {
@@ -47,7 +50,7 @@ describe('Selectors.PostList', () => {
assert.deepEqual(now, [
'1002',
'1001',
- 'date-0',
+ 'date-' + today.getTime(),
]);
// Show join/leave posts
@@ -73,7 +76,7 @@ describe('Selectors.PostList', () => {
assert.deepEqual(now, [
'1002',
'1001',
- 'date-0',
+ 'date-' + today.getTime(),
]);
// Hide join/leave posts
@@ -98,7 +101,7 @@ describe('Selectors.PostList', () => {
now = preparePostIdsForPostList(state, {postIds, lastViewedAt, indicateNewMessages});
assert.deepEqual(now, [
'1001',
- 'date-0',
+ 'date-' + today.getTime(),
]);
// always show join/leave posts for the current user
@@ -110,7 +113,7 @@ describe('Selectors.PostList', () => {
...state.entities.posts,
posts: {
...state.entities.posts.posts,
- 1002: {id: '1002', create_at: 1, type: Posts.POST_TYPES.JOIN_CHANNEL, props: {username: 'user'}},
+ 1002: {id: '1002', create_at: time + 1, type: Posts.POST_TYPES.JOIN_CHANNEL, props: {username: 'user'}},
},
},
},
@@ -121,20 +124,23 @@ describe('Selectors.PostList', () => {
assert.deepEqual(now, [
'1002',
'1001',
- 'date-0',
+ 'date-' + today.getTime(),
]);
});
it('new messages indicator', () => {
const preparePostIdsForPostList = makePreparePostIdsForPostList();
+ const time = Date.now();
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
const state = {
entities: {
posts: {
posts: {
- 1000: {id: '1000', create_at: 1000, type: ''},
- 1005: {id: '1005', create_at: 1005, type: ''},
- 1010: {id: '1010', create_at: 1010, type: ''},
+ 1000: {id: '1000', create_at: time + 1000, type: ''},
+ 1005: {id: '1005', create_at: time + 1005, type: ''},
+ 1010: {id: '1010', create_at: time + 1010, type: ''},
},
},
preferences: {
@@ -157,7 +163,7 @@ describe('Selectors.PostList', () => {
'1010',
'1005',
'1000',
- 'date-1000',
+ 'date-' + today.getTime(),
]);
now = preparePostIdsForPostList(state, {postIds, indicateNewMessages: true});
@@ -165,67 +171,72 @@ describe('Selectors.PostList', () => {
'1010',
'1005',
'1000',
- 'date-1000',
+ 'date-' + today.getTime(),
]);
- now = preparePostIdsForPostList(state, {postIds, lastViewedAt: 999, indicateNewMessages: false});
+ now = preparePostIdsForPostList(state, {postIds, lastViewedAt: time + 999, indicateNewMessages: false});
assert.deepEqual(now, [
'1010',
'1005',
'1000',
- 'date-1000',
+ 'date-' + today.getTime(),
]);
// Show new messages indicator before all posts
- now = preparePostIdsForPostList(state, {postIds, lastViewedAt: 999, indicateNewMessages: true});
+ now = preparePostIdsForPostList(state, {postIds, lastViewedAt: time + 999, indicateNewMessages: true});
assert.deepEqual(now, [
'1010',
'1005',
'1000',
START_OF_NEW_MESSAGES,
- 'date-1000',
+ 'date-' + today.getTime(),
]);
// Show indicator between posts
- now = preparePostIdsForPostList(state, {postIds, lastViewedAt: 1003, indicateNewMessages: true});
+ now = preparePostIdsForPostList(state, {postIds, lastViewedAt: time + 1003, indicateNewMessages: true});
assert.deepEqual(now, [
'1010',
'1005',
START_OF_NEW_MESSAGES,
'1000',
- 'date-1000',
+ 'date-' + today.getTime(),
]);
- now = preparePostIdsForPostList(state, {postIds, lastViewedAt: 1006, indicateNewMessages: true});
+ now = preparePostIdsForPostList(state, {postIds, lastViewedAt: time + 1006, indicateNewMessages: true});
assert.deepEqual(now, [
'1010',
START_OF_NEW_MESSAGES,
'1005',
'1000',
- 'date-1000',
+ 'date-' + today.getTime(),
]);
// Don't show indicator when all posts are read
- now = preparePostIdsForPostList(state, {postIds, lastViewedAt: 1020});
+ now = preparePostIdsForPostList(state, {postIds, lastViewedAt: time + 1020});
assert.deepEqual(now, [
'1010',
'1005',
'1000',
- 'date-1000',
+ 'date-' + today.getTime(),
]);
});
it('memoization', () => {
const preparePostIdsForPostList = makePreparePostIdsForPostList();
+ const time = Date.now();
+ const today = new Date();
+ const tomorrow = new Date((24 * 60 * 60 * 1000) + today.getTime());
+ today.setHours(0, 0, 0, 0);
+ tomorrow.setHours(0, 0, 0, 0);
// Posts 7 hours apart so they should appear on multiple days
const initialPosts = {
- 1001: {id: '1001', create_at: 1 * 60 * 60 * 1000, type: ''},
- 1002: {id: '1002', create_at: (1 * 60 * 60 * 1000) + 5, type: ''},
- 1003: {id: '1003', create_at: (1 * 60 * 60 * 1000) + 10, type: ''},
- 1004: {id: '1004', create_at: 25 * 60 * 60 * 1000, type: ''},
- 1005: {id: '1005', create_at: (25 * 60 * 60 * 1000) + 5, type: ''},
- 1006: {id: '1006', create_at: (25 * 60 * 60 * 1000) + 10, type: Posts.POST_TYPES.JOIN_CHANNEL},
+ 1001: {id: '1001', create_at: time, type: ''},
+ 1002: {id: '1002', create_at: time + 5, type: ''},
+ 1003: {id: '1003', create_at: time + 10, type: ''},
+ 1004: {id: '1004', create_at: tomorrow, type: ''},
+ 1005: {id: '1005', create_at: tomorrow + 5, type: ''},
+ 1006: {id: '1006', create_at: tomorrow + 10, type: Posts.POST_TYPES.JOIN_CHANNEL},
};
let state = {
entities: {
@@ -262,11 +273,11 @@ describe('Selectors.PostList', () => {
assert.deepEqual(now, [
'1006',
'1004',
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
START_OF_NEW_MESSAGES,
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
// No changes
@@ -276,11 +287,11 @@ describe('Selectors.PostList', () => {
assert.deepEqual(now, [
'1006',
'1004',
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
START_OF_NEW_MESSAGES,
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
// lastViewedAt changed slightly
@@ -292,15 +303,15 @@ describe('Selectors.PostList', () => {
assert.deepEqual(now, [
'1006',
'1004',
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
START_OF_NEW_MESSAGES,
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
// lastViewedAt changed a lot
- lastViewedAt += initialPosts['1003'].create_at + 1;
+ lastViewedAt = initialPosts['1003'].create_at + 1;
prev = now;
now = preparePostIdsForPostList(state, {postIds, lastViewedAt, indicateNewMessages: true});
@@ -309,10 +320,10 @@ describe('Selectors.PostList', () => {
'1006',
'1004',
START_OF_NEW_MESSAGES,
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
prev = now;
@@ -322,10 +333,10 @@ describe('Selectors.PostList', () => {
'1006',
'1004',
START_OF_NEW_MESSAGES,
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
// postIds changed, but still shallowly equal
@@ -338,10 +349,10 @@ describe('Selectors.PostList', () => {
'1006',
'1004',
START_OF_NEW_MESSAGES,
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
// Post changed, not in postIds
@@ -366,10 +377,10 @@ describe('Selectors.PostList', () => {
'1006',
'1004',
START_OF_NEW_MESSAGES,
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
// Post changed, in postIds
@@ -394,10 +405,10 @@ describe('Selectors.PostList', () => {
'1006',
'1004',
START_OF_NEW_MESSAGES,
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
// Filter changed
@@ -425,10 +436,10 @@ describe('Selectors.PostList', () => {
assert.deepEqual(now, [
'1004',
START_OF_NEW_MESSAGES,
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
prev = now;
@@ -437,10 +448,10 @@ describe('Selectors.PostList', () => {
assert.deepEqual(now, [
'1004',
START_OF_NEW_MESSAGES,
- 'date-90000000',
+ 'date-' + tomorrow.getTime(),
'1003',
'1001',
- 'date-3600000',
+ 'date-' + today.getTime(),
]);
});
});
diff --git a/index.js b/index.js
index 5746e3135..1b5b1e97e 100644
--- a/index.js
+++ b/index.js
@@ -3,6 +3,7 @@
import 'react-native/Libraries/Core/InitializeCore';
import {AppRegistry, Platform} from 'react-native';
+import 'react-native-gesture-handler';
import 'app/mattermost';
import ShareExtension from 'share_extension/android';
diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj
index 800f5c690..8727d9825 100644
--- a/ios/Mattermost.xcodeproj/project.pbxproj
+++ b/ios/Mattermost.xcodeproj/project.pbxproj
@@ -33,6 +33,7 @@
3C0B333879CE4730843D8584 /* Roboto-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D08F54E10DF14AE4BBF4E2B4 /* Roboto-MediumItalic.ttf */; };
3D38ABA732A34A9BB3294F90 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 349FBA7338E74D9BBD709528 /* EvilIcons.ttf */; };
3D7B4E6EE6B948AAA6A1E4E6 /* Roboto-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C46342B0E5FD4D878AF3A129 /* Roboto-BoldItalic.ttf */; };
+ 3F55075810254369AB39F032 /* libRNGestureHandler.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 476BA76E31644398A6012911 /* libRNGestureHandler.a */; };
3F876A0DC6314E27B16C8A96 /* libRNReactNativeDocViewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */; };
420A7328E12C4B72AEF420CE /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EDC04CBCF81642219D199CBB /* Octicons.ttf */; };
460A48EA5C6C4D8B8D9A2C75 /* Roboto-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 031EF04FB2D14EEFAACBAA1A /* Roboto-Italic.ttf */; };
@@ -438,6 +439,13 @@
remoteGlobalIDString = 7F43D6041F6BF9CA001FC614;
remoteInfo = "RNSVG-tvOS";
};
+ 7F46E07E21B5AF9E004060B8 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 920B7301B6F84DDD80677487 /* RNGestureHandler.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 134814201AA4EA6300B7C361;
+ remoteInfo = RNGestureHandler;
+ };
7F4C1F831FBE572B0029D1DF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
@@ -696,6 +704,7 @@
41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNReactNativeDocViewer.xcodeproj; path = "../node_modules/react-native-doc-viewer/ios/RNReactNativeDocViewer.xcodeproj"; sourceTree = ""; };
41F3AFE83AAF4B74878AB78A /* OpenSans-Italic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Italic.ttf"; path = "../assets/fonts/OpenSans-Italic.ttf"; sourceTree = ""; };
4246DC09024BB33A3E491E25 /* libPods-MattermostTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MattermostTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 476BA76E31644398A6012911 /* libRNGestureHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNGestureHandler.a; sourceTree = ""; };
4A22BC320BA04E18A7F2A4E6 /* libReactNativeExceptionHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libReactNativeExceptionHandler.a; sourceTree = ""; };
4B19E38FBCB94C57BB03B8E6 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = ""; };
51F5A483EA9F4A9A87ACFB59 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = ""; };
@@ -769,6 +778,7 @@
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; };
849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSafeArea.xcodeproj; path = "../node_modules/react-native-safe-area/ios/RNSafeArea.xcodeproj"; sourceTree = ""; };
8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-LightItalic.ttf"; path = "../assets/fonts/Roboto-LightItalic.ttf"; sourceTree = ""; };
+ 920B7301B6F84DDD80677487 /* RNGestureHandler.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNGestureHandler.xcodeproj; path = "../node_modules/react-native-gesture-handler/ios/RNGestureHandler.xcodeproj"; sourceTree = ""; };
9263CF9B16054263B13EA23B /* libRNSafeArea.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSafeArea.a; sourceTree = ""; };
A2968536BEC74A4ABBB73BD9 /* RCTYouTube.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTYouTube.xcodeproj; path = "../node_modules/react-native-youtube/RCTYouTube.xcodeproj"; sourceTree = ""; };
A734E00E7E184582A877F2B3 /* Roboto-Thin.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Thin.ttf"; path = "../assets/fonts/Roboto-Thin.ttf"; sourceTree = ""; };
@@ -849,6 +859,7 @@
3F876A0DC6314E27B16C8A96 /* libRNReactNativeDocViewer.a in Frameworks */,
8D26455C994F46C39B1392F2 /* libRNSafeArea.a in Frameworks */,
C3BD62ED5B5940AB8C0AC678 /* libz.tbd in Frameworks */,
+ 3F55075810254369AB39F032 /* libRNGestureHandler.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1098,6 +1109,7 @@
B89192186C764B9FA473403A /* libRCTVideo.a */,
6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */,
9263CF9B16054263B13EA23B /* libRNSafeArea.a */,
+ 476BA76E31644398A6012911 /* libRNGestureHandler.a */,
);
name = "Recovered References";
sourceTree = "";
@@ -1196,6 +1208,14 @@
name = Products;
sourceTree = "";
};
+ 7F46E07B21B5AF9E004060B8 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 7F46E07F21B5AF9E004060B8 /* libRNGestureHandler.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
7F5CA957208FE38F004F91CE /* Products */ = {
isa = PBXGroup;
children = (
@@ -1393,6 +1413,7 @@
DF9DAAAA482343F3910A1A4C /* RCTVideo.xcodeproj */,
41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */,
849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */,
+ 920B7301B6F84DDD80677487 /* RNGestureHandler.xcodeproj */,
);
name = Libraries;
sourceTree = "";
@@ -1666,6 +1687,10 @@
ProductGroup = 7FF31C0F21330B4200680B75 /* Products */;
ProjectRef = 7FF31C0E21330B4200680B75 /* RNFetchBlob.xcodeproj */;
},
+ {
+ ProductGroup = 7F46E07B21B5AF9E004060B8 /* Products */;
+ ProjectRef = 920B7301B6F84DDD80677487 /* RNGestureHandler.xcodeproj */;
+ },
{
ProductGroup = 7FDB92761F706F45006CDFD1 /* Products */;
ProjectRef = 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */;
@@ -2022,6 +2047,13 @@
remoteRef = 7F43D63A1F6BF9EB001FC614 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
+ 7F46E07F21B5AF9E004060B8 /* libRNGestureHandler.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRNGestureHandler.a;
+ remoteRef = 7F46E07E21B5AF9E004060B8 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
7F4C1F841FBE572B0029D1DF /* libfishhook.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -2436,11 +2468,15 @@
"$(SRCROOT)/../node_modules/react-native-video/ios",
"$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
"$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
+ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
);
INFOPLIST_FILE = MattermostTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- LIBRARY_SEARCH_PATHS = "$(inherited)";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
+ );
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2463,11 +2499,15 @@
"$(SRCROOT)/../node_modules/react-native-video/ios",
"$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
"$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
+ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
);
INFOPLIST_FILE = MattermostTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- LIBRARY_SEARCH_PATHS = "$(inherited)";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
+ );
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2512,6 +2552,7 @@
"$(SRCROOT)../node_modules/react-native-webview/ios",
"$(SRCROOT)../node_modules/react-native-svg/ios/**",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
+ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
);
INFOPLIST_FILE = Mattermost/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
@@ -2563,6 +2604,7 @@
"$(SRCROOT)../node_modules/react-native-webview/ios",
"$(SRCROOT)../node_modules/react-native-svg/ios/**",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
+ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
);
INFOPLIST_FILE = Mattermost/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
@@ -2619,10 +2661,15 @@
"$(SRCROOT)/../node_modules/react-native-video/ios/**",
"$(SRCROOT)/../node_modules/react-native-tableview/**",
"$(SRCROOT)/../node_modules/react-native-device-info/ios/**",
+ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
);
INFOPLIST_FILE = MattermostShare/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
+ );
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
@@ -2676,10 +2723,15 @@
"$(SRCROOT)/../node_modules/react-native-video/ios/**",
"$(SRCROOT)/../node_modules/react-native-tableview/**",
"$(SRCROOT)/../node_modules/react-native-device-info/ios/**",
+ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**",
);
INFOPLIST_FILE = MattermostShare/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
+ );
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
diff --git a/package-lock.json b/package-lock.json
index dbae32a17..6f246f96f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3800,7 +3800,7 @@
},
"event-target-shim": {
"version": "1.1.1",
- "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.1.1.tgz",
+ "resolved": "http://registry.npmjs.org/event-target-shim/-/event-target-shim-1.1.1.tgz",
"integrity": "sha1-qG5e5r2qFgVEddp5fM3fDFVphJE="
},
"eventemitter3": {
@@ -11228,6 +11228,16 @@
"resolved": "https://registry.npmjs.org/react-native-exception-handler/-/react-native-exception-handler-2.10.2.tgz",
"integrity": "sha512-hcPXfjSTSEbJYhUUbgarKZNwJFG2rehbJVW5ZbQc2rVkFor6tS4diyqufkWayS0mKg+N/Q3sP1w0j3QWv+u4mg=="
},
+ "react-native-gesture-handler": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.0.10.tgz",
+ "integrity": "sha512-GNVZ+rbwfi147qOxIvWyfHcTswr+CRT/bSy/Gr5Dz17/tWmomf9D6tGr1yJhyW8IhEZ307KLBWRCXLBM9Q2YQg==",
+ "requires": {
+ "hoist-non-react-statics": "^2.3.1",
+ "invariant": "^2.2.2",
+ "prop-types": "^15.5.10"
+ }
+ },
"react-native-image-gallery": {
"version": "github:enahum/react-native-image-gallery#a98b1051e94f5a394541ca1ff9b15e2c9ffed84f",
"from": "github:enahum/react-native-image-gallery#a98b1051e94f5a394541ca1ff9b15e2c9ffed84f",
diff --git a/package.json b/package.json
index cd802bf99..adfacacda 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"react-native-doc-viewer": "2.7.8",
"react-native-document-picker": "2.1.0",
"react-native-exception-handler": "2.10.2",
+ "react-native-gesture-handler": "1.0.10",
"react-native-image-gallery": "github:enahum/react-native-image-gallery#a98b1051e94f5a394541ca1ff9b15e2c9ffed84f",
"react-native-image-picker": "0.27.1",
"react-native-keyboard-aware-scroll-view": "0.7.4",
diff --git a/test/setup.js b/test/setup.js
index b316378dd..6ac9e1113 100644
--- a/test/setup.js
+++ b/test/setup.js
@@ -9,10 +9,23 @@ configure({adapter: new Adapter()});
jest.mock('NativeModules', () => {
return {
+ UIManager: {
+ RCTView: {
+ directEventTypes: {},
+ },
+ },
BlurAppScreen: () => true,
MattermostManaged: {
getConfig: jest.fn(),
},
+ RNGestureHandlerModule: {
+ State: {
+ BEGAN: 'BEGAN',
+ FAILED: 'FAILED',
+ ACTIVE: 'ACTIVE',
+ END: 'END',
+ },
+ },
};
});
jest.mock('NativeEventEmitter');