diff --git a/app/components/channel_intro/channel_intro.js b/app/components/channel_intro/channel_intro.js index 2615fe459..2a853a989 100644 --- a/app/components/channel_intro/channel_intro.js +++ b/app/components/channel_intro/channel_intro.js @@ -3,10 +3,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import { - Text, - View, -} from 'react-native'; +import {Platform, Text, View} from 'react-native'; import {injectIntl, intlShape} from 'react-intl'; import {displayUsername} from '@mm-redux/utils/user_utils'; @@ -386,6 +383,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { marginHorizontal: 12, marginBottom: 12, overflow: 'hidden', + ...Platform.select({ + android: { + scaleY: -1, + }, + }), }, displayName: { color: theme.centerChannelColor, diff --git a/app/components/post_list/combined_user_activity/combined_user_activity.tsx b/app/components/post_list/combined_user_activity/combined_user_activity.tsx index 96e34fd89..f3d918fb6 100644 --- a/app/components/post_list/combined_user_activity/combined_user_activity.tsx +++ b/app/components/post_list/combined_user_activity/combined_user_activity.tsx @@ -3,7 +3,7 @@ import React from 'react'; import {injectIntl, intlShape} from 'react-intl'; -import {Keyboard, View} from 'react-native'; +import {Keyboard, StyleProp, View, ViewStyle} from 'react-native'; import {showModalOverCurrentContext} from '@actions/navigation'; import Markdown from '@components/markdown'; @@ -31,6 +31,7 @@ type Props = { testID?: string; theme: Theme; usernamesById: Record; + style?: StyleProp; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { @@ -87,7 +88,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { const CombinedUserActivity = ({ canDelete, currentUserId, currentUsername, intl, post, - showJoinLeave, testID, theme, usernamesById, + showJoinLeave, testID, theme, usernamesById, style, }: Props) => { const itemTestID = `${testID}.${post.id}`; const textStyles = getMarkdownTextStyles(theme); @@ -215,6 +216,7 @@ const CombinedUserActivity = ({ return ( ; testID: string; theme: Theme; } diff --git a/app/components/post_list/post/post.tsx b/app/components/post_list/post/post.tsx index 64fc844d4..8876ecabe 100644 --- a/app/components/post_list/post/post.tsx +++ b/app/components/post_list/post/post.tsx @@ -44,6 +44,7 @@ type PostProps = { showPermalink: (intl: typeof intlShape, teamName: string, postId: string) => null; skipFlaggedHeader?: boolean; skipPinnedHeader?: boolean; + style?: StyleProp; teammateNameDisplay: string; testID?: string; theme: Theme @@ -92,10 +93,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { const Post = ({ canDelete, enablePostUsernameOverride, highlight, highlightPinnedOrFlagged = true, intl, isConsecutivePost, isFirstReply, isFlagged, isLastReply, location, post, removePost, rootPostAuthor, shouldRenderReplyButton, skipFlaggedHeader, skipPinnedHeader, showAddReaction = true, showPermalink, - teammateNameDisplay, testID, theme, + teammateNameDisplay, testID, theme, style, }: PostProps) => { const pressDetected = useRef(false); - const style = getStyleSheet(theme); + const styles = getStyleSheet(theme); const handlePress = preventDoubleTap(() => { pressDetected.current = true; @@ -159,23 +160,23 @@ const Post = ({ const highlightFlagged = isFlagged && !skipFlaggedHeader; const hightlightPinned = post.is_pinned && !skipPinnedHeader; const itemTestID = `${testID}.${post.id}`; - const rightColumnStyle = [style.rightColumn, (post.root_id && isLastReply && style.rightColumnPadding)]; - const pendingPostStyle: StyleProp | undefined = isPostPendingOrFailed(post) ? style.pendingPost : undefined; + const rightColumnStyle = [styles.rightColumn, (post.root_id && isLastReply && styles.rightColumnPadding)]; + const pendingPostStyle: StyleProp | undefined = isPostPendingOrFailed(post) ? styles.pendingPost : undefined; const isAutoResponder = fromAutoResponder(post); let highlightedStyle: StyleProp; if (highlight) { - highlightedStyle = style.highlight; + highlightedStyle = styles.highlight; } else if ((highlightFlagged || hightlightPinned) && highlightPinnedOrFlagged) { - highlightedStyle = style.highlightPinnedOrFlagged; + highlightedStyle = styles.highlightPinnedOrFlagged; } let header: ReactNode; let postAvatar: ReactNode; let consecutiveStyle: StyleProp; if (isConsecutivePost) { - consecutiveStyle = style.consective; - postAvatar = ; + consecutiveStyle = styles.consective; + postAvatar = ; } else { postAvatar = isAutoResponder ? ( @@ -235,7 +236,7 @@ const Post = ({ return ( - + {postAvatar} {header} diff --git a/app/components/post_list/post_list.tsx b/app/components/post_list/post_list.tsx index 07c072100..940f2ca9b 100644 --- a/app/components/post_list/post_list.tsx +++ b/app/components/post_list/post_list.tsx @@ -1,9 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {ReactElement, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; +import React, {ReactElement, useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; import {injectIntl, intlShape} from 'react-intl'; -import {DeviceEventEmitter, FlatList, Platform, RefreshControl, StyleSheet, ViewToken} from 'react-native'; +import {DeviceEventEmitter, FlatList, NativeScrollEvent, NativeSyntheticEvent, Platform, StyleSheet, ViewToken} from 'react-native'; import {DeepLinkTypes, NavigationTypes} from '@constants'; import * as Screens from '@constants/screen'; @@ -27,6 +27,7 @@ import DateSeparator from './date_separator'; import MoreMessagesButton from './more_messages_button'; import NewMessagesLine from './new_message_line'; import Post from './post'; +import PostListRefreshControl from './post_list_refresh_control'; type PostListProps = { channelId?: string; @@ -79,6 +80,13 @@ const styles = StyleSheet.create({ postListContent: { paddingTop: 5, }, + scale: { + ...Platform.select({ + android: { + scaleY: -1, + }, + }), + }, }); const buildExtraData = makeExtraData(); @@ -95,6 +103,7 @@ const PostList = ({ const onScrollEndIndexListener = useRef(); const onViewableItemsChangedListener = useRef(); const [refreshing, setRefreshing] = useState(false); + const [offsetY, setOffsetY] = useState(0); const registerViewableItemsListener = useCallback((listener) => { onViewableItemsChangedListener.current = listener; @@ -181,6 +190,7 @@ const PostList = ({ theme={theme} moreMessages={moreNewMessages && checkForPostId} testID={`${testID}.new_messages_line`} + style={styles.scale} /> ); } else if (isDateLine(item)) { @@ -188,6 +198,7 @@ const PostList = ({ ); } @@ -195,6 +206,7 @@ const PostList = ({ if (isCombinedUserActivityPost(item)) { const postProps = { postId: item, + style: styles.scale, testID: `${testID}.combined_user_activity`, theme, }; @@ -232,6 +244,7 @@ const PostList = ({ @@ -247,6 +260,17 @@ const PostList = ({ }); }, []); + const onScroll = useCallback((event: NativeSyntheticEvent) => { + if (Platform.OS === 'android') { + const {y} = event.nativeEvent.contentOffset; + if (y === 0) { + setOffsetY(y); + } else if (offsetY === 0 && y !== 0) { + setOffsetY(y); + } + } + }, [offsetY]); + useResetNativeScrollView(scrollViewNativeID, postIds); useEffect(() => { @@ -300,46 +324,47 @@ const PostList = ({ } }, [initialIndex, highlightPostId]); - const refreshControl = useMemo(() => ( - - ), [refreshing, theme]); + ); return ( <> - + + {list} + {showMoreMessagesButton && void; + refreshing: boolean; + theme: Theme; +} + +const style = StyleSheet.create({ + container: { + flex: 1, + scaleY: -1, + }, +}); + +const PostListRefreshControl = ({children, enabled, onRefresh, refreshing, theme}: Props) => { + const props = { + colors: [theme.onlineIndicator, theme.awayIndicator, theme.dndIndicator], + onRefresh, + refreshing, + tintColor: theme.centerChannelColor, + }; + + if (Platform.OS === 'android') { + return ( + + {children} + + ); + } + + const refreshControl = ; + + return React.cloneElement( + children, + {refreshControl, inverted: true}, + ); +}; + +export default PostListRefreshControl; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9b9da47bd..1036cf7d0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -693,7 +693,7 @@ SPEC CHECKSUMS: BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872 DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b - FBReactNativeSpec: 74185edd6e8e5cb35512747bd840f8f9ad2381ff + FBReactNativeSpec: cef0cc6d50abc92e8cf52f140aa22b5371cfec0b glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62 jail-monkey: 01cd0a75aa1034d08fd851869e6e6c3b063242d7 libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0 diff --git a/package-lock.json b/package-lock.json index 93bf60e57..eac9ef0a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "requires": true, "packages": { "": { - "version": "1.44.1", + "version": "1.45.0", "hasInstallScript": true, "license": "Apache 2.0", "dependencies": { @@ -62143,8 +62143,7 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==", - "requires": { -} + "requires": {} }, "xcode": { "version": "3.0.1",