post menu and file attachments (#2414)
* post menu and file attachments * Fix post list selector test * Use new Gesture handler to avoid gesture conflicts in post menu and reaction list * Allow reaction list to scroll the header on Android * Feedback review * Add some comments * Fix eslint
This commit is contained in:
parent
2a6ad4f9ae
commit
2184696e65
24 changed files with 710 additions and 616 deletions
35
NOTICE.txt
35
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.
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ export default class PostBody extends PureComponent {
|
|||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
navBarTransparent: true,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ export default class Reactions extends PureComponent {
|
|||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
navBarTransparent: true,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={styles.viewport}>
|
||||
<TapGestureHandler
|
||||
maxDurationMs={100000}
|
||||
ref={this.masterRef}
|
||||
maxDeltaY={lastSnap - this.snapPoints[0]}
|
||||
>
|
||||
<View
|
||||
ref={this.setBackdropRef}
|
||||
style={styles.backdrop}
|
||||
pointerEvents='box-only'
|
||||
onTouchEnd={this.handleTouchEnd}
|
||||
{...this.secondaryPanGesture.panHandlers}
|
||||
/>
|
||||
<SlideUpPanelIndicator
|
||||
containerPosition={containerPosition}
|
||||
panHandlers={this.secondaryPanGesture.panHandlers}
|
||||
/>
|
||||
<Animated.View
|
||||
ref={this.setViewRef}
|
||||
style={[containerPosition, styles.container]}
|
||||
{...this.mainPanGesture.panHandlers}
|
||||
style={StyleSheet.absoluteFill}
|
||||
pointerEvents='box-none'
|
||||
>
|
||||
<View style={{maxHeight: (this.props.containerHeight - this.props.headerHeight - CONTAINER_MARGIN)}}>
|
||||
{children}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
<TapGestureHandler
|
||||
waitFor={this.backdropRef}
|
||||
onHandlerStateChange={this.onSingleTap}
|
||||
>
|
||||
<Animated.View style={styles.viewport}>
|
||||
<PanGestureHandler
|
||||
simultaneousHandlers={[this.scrollRef, this.masterRef]}
|
||||
waitFor={this.headerRef}
|
||||
shouldCancelWhenOutside={false}
|
||||
onGestureEvent={this.onGestureEvent}
|
||||
onHandlerStateChange={this.onHandlerStateChange}
|
||||
ref={this.backdropRef}
|
||||
>
|
||||
|
||||
<Animated.View
|
||||
style={styles.backdrop}
|
||||
pointerEvents='box-only'
|
||||
/>
|
||||
</PanGestureHandler>
|
||||
</Animated.View>
|
||||
</TapGestureHandler>
|
||||
<Animated.View style={[StyleSheet.absoluteFill, translateStyle]}>
|
||||
<PanGestureHandler
|
||||
simultaneousHandlers={[this.scrollRef, this.masterRef]}
|
||||
waitFor={this.headerRef}
|
||||
shouldCancelWhenOutside={false}
|
||||
onGestureEvent={this.onGestureEvent}
|
||||
onHandlerStateChange={this.onHeaderHandlerStateChange}
|
||||
>
|
||||
<Animated.View>
|
||||
<SlideUpPanelIndicator/>
|
||||
{header(this.headerRef)}
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
<PanGestureHandler
|
||||
ref={this.panRef}
|
||||
simultaneousHandlers={[this.scrollRef, this.masterRef]}
|
||||
waitFor={this.headerRef}
|
||||
shouldCancelWhenOutside={false}
|
||||
onGestureEvent={this.onGestureEvent}
|
||||
onHandlerStateChange={this.onHandlerStateChange}
|
||||
>
|
||||
<Animated.View style={[styles.container, !header && styles.border]}>
|
||||
<NativeViewGestureHandler
|
||||
ref={this.scrollRef}
|
||||
waitFor={this.masterRef}
|
||||
simultaneousHandlers={this.panRef}
|
||||
>
|
||||
<Animated.ScrollView
|
||||
ref={this.scrollViewRef}
|
||||
bounces={false}
|
||||
onScrollBeginDrag={this.onRegisterLastScroll}
|
||||
scrollEventThrottle={1}
|
||||
>
|
||||
{children}
|
||||
</Animated.ScrollView>
|
||||
</NativeViewGestureHandler>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</TapGestureHandler>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -241,6 +304,8 @@ const styles = StyleSheet.create({
|
|||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
border: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
borderTopRightRadius: 2,
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Animated.View
|
||||
style={[containerPosition, styles.dragIndicatorContainer]}
|
||||
{...panHandlers}
|
||||
>
|
||||
<View style={styles.dragIndicator}/>
|
||||
</Animated.View>
|
||||
);
|
||||
export default function slideUpPanelIndicator() {
|
||||
if (Platform.OS === 'android') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={styles.dragIndicatorContainer}
|
||||
>
|
||||
<View style={styles.dragIndicator}/>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={style.container} >
|
||||
<Touchable
|
||||
onPressOut={onPress}
|
||||
onPress={this.handleOnPress}
|
||||
{...touchableProps}
|
||||
style={style.row}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<View style={style.icon}>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={style.flex}>
|
||||
<View style={style.container}>
|
||||
<SlideUpPanel
|
||||
allowStayMiddle={false}
|
||||
alwaysCaptureContainerMove={true}
|
||||
ref={this.refSlideUpPanel}
|
||||
marginFromTop={marginFromTop}
|
||||
onRequestClose={this.close}
|
||||
initialPosition={initialPosition}
|
||||
initialPosition={325}
|
||||
>
|
||||
{options}
|
||||
</SlideUpPanel>
|
||||
|
|
@ -407,7 +405,7 @@ export default class PostOptions extends PureComponent {
|
|||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
flex: {
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,92 +1,94 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ReactionHeader should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"borderTopLeftRadius": 10,
|
||||
"borderTopRightRadius": 10,
|
||||
"height": 36.5,
|
||||
"paddingHorizontal": 0,
|
||||
<Handler>
|
||||
<AnimatedComponent
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"borderTopLeftRadius": 10,
|
||||
"borderTopRightRadius": 10,
|
||||
"height": 36.5,
|
||||
"paddingHorizontal": 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
<ScrollView
|
||||
alwaysBounceHorizontal={false}
|
||||
horizontal={true}
|
||||
overScrollMode="never"
|
||||
>
|
||||
<ReactionHeaderItem
|
||||
count={2}
|
||||
emojiName="smile"
|
||||
highlight={true}
|
||||
onPress={[Function]}
|
||||
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",
|
||||
<ScrollView
|
||||
alwaysBounceHorizontal={false}
|
||||
horizontal={true}
|
||||
overScrollMode="never"
|
||||
>
|
||||
<ReactionHeaderItem
|
||||
count={2}
|
||||
emojiName="smile"
|
||||
highlight={true}
|
||||
onPress={[Function]}
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
<ReactionHeaderItem
|
||||
count={1}
|
||||
emojiName="+1"
|
||||
highlight={false}
|
||||
onPress={[Function]}
|
||||
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",
|
||||
/>
|
||||
<ReactionHeaderItem
|
||||
count={1}
|
||||
emojiName="+1"
|
||||
highlight={false}
|
||||
onPress={[Function]}
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
/>
|
||||
</ScrollView>
|
||||
</AnimatedComponent>
|
||||
</Handler>
|
||||
`;
|
||||
|
||||
exports[`ReactionHeader should match snapshot, renderContent 1`] = `
|
||||
|
|
|
|||
|
|
@ -9,231 +9,155 @@ exports[`ReactionList should match snapshot 1`] = `
|
|||
}
|
||||
>
|
||||
<Connect(SlideUpPanel)
|
||||
header={[Function]}
|
||||
headerHeight={37.5}
|
||||
initialPosition={0.55}
|
||||
onRequestClose={[Function]}
|
||||
>
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 45,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionRow
|
||||
emojiName="+1"
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
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_2",
|
||||
"username": "username_2",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderBottomWidth": 1,
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"height": 37.5,
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionHeader
|
||||
onSelectReaction={[Function]}
|
||||
reactions={
|
||||
Array [
|
||||
Object {
|
||||
"count": 2,
|
||||
"name": "all_emojis",
|
||||
},
|
||||
Object {
|
||||
"count": 1,
|
||||
"name": "+1",
|
||||
"reactions": Array [
|
||||
Object {
|
||||
"emoji_name": "+1",
|
||||
"user_id": "user_id_2",
|
||||
},
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 45,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionRow
|
||||
emojiName="smile"
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"count": 1,
|
||||
"name": "smile",
|
||||
"reactions": Array [
|
||||
Object {
|
||||
"emoji_name": "smile",
|
||||
"user_id": "user_id_1",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
selected="all_emojis"
|
||||
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",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<ScrollView
|
||||
bounce={false}
|
||||
onScroll={[Function]}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 45,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionRow
|
||||
emojiName="+1"
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
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_2",
|
||||
"username": "username_2",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"height": 45,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
>
|
||||
<ReactionRow
|
||||
emojiName="smile"
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
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",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</React.Fragment>
|
||||
}
|
||||
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",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Connect(SlideUpPanel)>
|
||||
</View>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={styles.container}>
|
||||
<ScrollView
|
||||
alwaysBounceHorizontal={false}
|
||||
horizontal={true}
|
||||
overScrollMode='never'
|
||||
>
|
||||
{this.renderReactionHeaderItems()}
|
||||
</ScrollView>
|
||||
</View>
|
||||
<NativeViewGestureHandler
|
||||
ref={this.props.forwardedRef}
|
||||
>
|
||||
<Animated.View style={styles.container}>
|
||||
<ScrollView
|
||||
alwaysBounceHorizontal={false}
|
||||
horizontal={true}
|
||||
overScrollMode='never'
|
||||
>
|
||||
{this.renderReactionHeaderItems()}
|
||||
</ScrollView>
|
||||
</Animated.View>
|
||||
</NativeViewGestureHandler>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ReactionHeader
|
||||
selected={selected}
|
||||
onSelectReaction={this.handleOnSelectReaction}
|
||||
reactions={sortedReactionsForHeader}
|
||||
theme={theme}
|
||||
forwardedRef={forwardedRef}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme,
|
||||
} = this.props;
|
||||
const {
|
||||
selected,
|
||||
sortedReactionsForHeader,
|
||||
} = this.state;
|
||||
const style = getStyleSheet(theme);
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return (
|
||||
<View style={style.flex}>
|
||||
|
|
@ -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}
|
||||
>
|
||||
<React.Fragment>
|
||||
<View style={style.headerContainer}>
|
||||
<ReactionHeader
|
||||
selected={selected}
|
||||
onSelectReaction={this.handleOnSelectReaction}
|
||||
reactions={sortedReactionsForHeader}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<ScrollView
|
||||
ref={this.refScrollView}
|
||||
bounce={false}
|
||||
onScroll={this.handleScroll}
|
||||
>
|
||||
{this.renderReactionRows()}
|
||||
</ScrollView>
|
||||
</React.Fragment>
|
||||
{this.renderReactionRows()}
|
||||
</SlideUpPanel>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -233,8 +205,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
headerContainer: {
|
||||
height: 37.5,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
rowContainer: {
|
||||
justifyContent: 'center',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
1
index.js
1
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';
|
||||
|
|
|
|||
|
|
@ -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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
4A22BC320BA04E18A7F2A4E6 /* libReactNativeExceptionHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libReactNativeExceptionHandler.a; sourceTree = "<group>"; };
|
||||
4B19E38FBCB94C57BB03B8E6 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
|
|
@ -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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
9263CF9B16054263B13EA23B /* libRNSafeArea.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSafeArea.a; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
|
|
@ -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 = "<group>";
|
||||
|
|
@ -1196,6 +1208,14 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F46E07B21B5AF9E004060B8 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F46E07F21B5AF9E004060B8 /* libRNGestureHandler.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F5CA957208FE38F004F91CE /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1393,6 +1413,7 @@
|
|||
DF9DAAAA482343F3910A1A4C /* RCTVideo.xcodeproj */,
|
||||
41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */,
|
||||
849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */,
|
||||
920B7301B6F84DDD80677487 /* RNGestureHandler.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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++",
|
||||
|
|
|
|||
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Reference in a new issue