Page MenuHomePhabricator

D6720.diff
No OneTemporary

D6720.diff

diff --git a/native/chat/fullscreen-thread-media-gallery.react.js b/native/chat/fullscreen-thread-media-gallery.react.js
--- a/native/chat/fullscreen-thread-media-gallery.react.js
+++ b/native/chat/fullscreen-thread-media-gallery.react.js
@@ -1,17 +1,82 @@
// @flow
import * as React from 'react';
-import { Text } from 'react-native';
+import { Text, View, TouchableOpacity } from 'react-native';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import type { ChatNavigationProp } from './chat.react.js';
+import ThreadSettingsMediaGallery from './settings/thread-settings-media-gallery.react.js';
import type { NavigationRoute } from '../navigation/route-names.js';
+import { useStyles } from '../themes/colors.js';
+import type { VerticalBounds } from '../types/layout-types.js';
export type FullScreenThreadMediaGalleryParams = {
+threadInfo: ThreadInfo,
};
+const Tabs = {
+ All: 'ALL',
+ Images: 'IMAGES',
+ Videos: 'VIDEOS',
+};
+
+type FilterBarProps = {
+ +setActiveTab: (tab: string) => void,
+ +activeTab: string,
+};
+
+function FilterBar(props: FilterBarProps): React.Node {
+ const styles = useStyles(unboundStyles);
+ const { setActiveTab, activeTab } = props;
+
+ const allTabsOnPress = React.useCallback(
+ () => setActiveTab(Tabs.All),
+ [setActiveTab],
+ );
+
+ const imagesTabOnPress = React.useCallback(
+ () => setActiveTab(Tabs.Images),
+ [setActiveTab],
+ );
+
+ const videosTabOnPress = React.useCallback(
+ () => setActiveTab(Tabs.Videos),
+ [setActiveTab],
+ );
+
+ const tabStyles = (currentTab: string) =>
+ currentTab === activeTab ? styles.tabActiveItem : styles.tabItem;
+
+ return (
+ <View style={styles.filterBar}>
+ <View style={styles.tabNavigator}>
+ <TouchableOpacity
+ key={Tabs.All}
+ style={tabStyles(Tabs.All)}
+ onPress={allTabsOnPress}
+ >
+ <Text style={styles.tabText}>{Tabs.All}</Text>
+ </TouchableOpacity>
+ <TouchableOpacity
+ key={Tabs.Images}
+ style={tabStyles(Tabs.Images)}
+ onPress={imagesTabOnPress}
+ >
+ <Text style={styles.tabText}>{Tabs.Images}</Text>
+ </TouchableOpacity>
+ <TouchableOpacity
+ key={Tabs.Videos}
+ style={tabStyles(Tabs.Videos)}
+ onPress={videosTabOnPress}
+ >
+ <Text style={styles.tabText}>{Tabs.Videos}</Text>
+ </TouchableOpacity>
+ </View>
+ </View>
+ );
+}
+
type FullScreenThreadMediaGalleryProps = {
+navigation: ChatNavigationProp<'FullScreenThreadMediaGallery'>,
+route: NavigationRoute<'FullScreenThreadMediaGallery'>,
@@ -20,8 +85,95 @@
function FullScreenThreadMediaGallery(
props: FullScreenThreadMediaGalleryProps,
): React.Node {
- const { id } = props.route.params.threadInfo;
- return <Text>{id}</Text>;
+ const { threadInfo } = props.route.params;
+ const { id } = threadInfo;
+ const styles = useStyles(unboundStyles);
+
+ const [activeTab, setActiveTab] = React.useState(Tabs.All);
+ const flatListContainerRef = React.useRef<?React.ElementRef<typeof View>>();
+ const [verticalBounds, setVerticalBounds] =
+ React.useState<?VerticalBounds>(null);
+
+ const onFlatListContainerLayout = React.useCallback(() => {
+ if (!flatListContainerRef.current) {
+ return;
+ }
+
+ flatListContainerRef.current.measure(
+ (x, y, width, height, pageX, pageY) => {
+ if (
+ height === null ||
+ height === undefined ||
+ pageY === null ||
+ pageY === undefined
+ ) {
+ return;
+ }
+ setVerticalBounds({ height, y: pageY });
+ },
+ );
+ }, [flatListContainerRef]);
+
+ return (
+ <View
+ style={styles.container}
+ ref={flatListContainerRef}
+ onLayout={onFlatListContainerLayout}
+ >
+ <FilterBar setActiveTab={setActiveTab} activeTab={activeTab} />
+ <ThreadSettingsMediaGallery
+ threadID={id}
+ verticalBounds={verticalBounds}
+ limit={21}
+ offset={0}
+ activeTab={activeTab}
+ />
+ </View>
+ );
}
-export default FullScreenThreadMediaGallery;
+const unboundStyles = {
+ container: {
+ marginBottom: 120,
+ },
+ filterBar: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ marginTop: 20,
+ marginBottom: 40,
+ },
+ tabNavigator: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'flex-start',
+ position: 'absolute',
+ width: '90%',
+ padding: 0,
+ },
+ tabActiveItem: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'floatingButtonBackground',
+ flex: 1,
+ height: 30,
+ borderRadius: 8,
+ },
+ tabItem: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'listInputBackground',
+ flex: 1,
+ height: 30,
+ },
+ tabText: {
+ color: 'floatingButtonLabel',
+ },
+};
+
+const MemoizedFullScreenMediaGallery: React.ComponentType<FullScreenThreadMediaGalleryProps> =
+ React.memo(FullScreenThreadMediaGallery);
+
+export default MemoizedFullScreenMediaGallery;
diff --git a/native/chat/settings/thread-settings-media-gallery.react.js b/native/chat/settings/thread-settings-media-gallery.react.js
--- a/native/chat/settings/thread-settings-media-gallery.react.js
+++ b/native/chat/settings/thread-settings-media-gallery.react.js
@@ -29,6 +29,8 @@
+threadID: string,
+limit: number,
+verticalBounds: ?VerticalBounds,
+ +offset?: number,
+ +activeTab?: string,
};
function ThreadSettingsMediaGallery(
@@ -46,8 +48,7 @@
// E.g. 16px, media, galleryItemGap, media, galleryItemGap, media, 16px
const galleryItemWidth =
(width - 32 - (numColumns - 1) * galleryItemGap) / numColumns;
-
- const { threadID, limit, verticalBounds } = props;
+ const { threadID, limit, verticalBounds, offset, activeTab } = props;
const [mediaInfos, setMediaInfos] = React.useState([]);
const callFetchThreadMedia = useServerCall(fetchThreadMedia);
@@ -83,6 +84,17 @@
};
}, [galleryItemWidth, styles.media, styles.mediaContainer]);
+ const filteredMediaInfos = React.useMemo(() => {
+ if (activeTab === 'ALL') {
+ return mediaInfos;
+ } else if (activeTab === 'IMAGES') {
+ return mediaInfos.filter(mediaInfo => mediaInfo.type === 'photo');
+ } else if (activeTab === 'VIDEOS') {
+ return mediaInfos.filter(mediaInfo => mediaInfo.type === 'video');
+ }
+ return mediaInfos;
+ }, [activeTab, mediaInfos]);
+
const renderItem = React.useCallback(
({ item, index }) => (
<MediaGalleryItem
@@ -96,12 +108,26 @@
[threadID, verticalBounds, memoizedStyles],
);
+ const onEndReached = React.useCallback(async () => {
+ // As the FlatList fetches more media, we set the offset to be the length
+ // of mediaInfos. This will ensure that the next set of media is retrieved
+ // from the starting point.
+ const result = await callFetchThreadMedia({
+ threadID,
+ limit,
+ offset: mediaInfos.length,
+ });
+ setMediaInfos([...mediaInfos, ...result.media]);
+ }, [callFetchThreadMedia, mediaInfos, threadID, limit]);
+
return (
<View style={styles.flatListContainer}>
<FlatList
- data={mediaInfos}
+ data={filteredMediaInfos}
numColumns={numColumns}
renderItem={renderItem}
+ onEndReached={offset !== undefined ? onEndReached : null}
+ onEndReachedThreshold={1}
/>
</View>
);

File Metadata

Mime Type
text/plain
Expires
Thu, Dec 26, 5:33 AM (5 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2702717
Default Alt Text
D6720.diff (7 KB)

Event Timeline