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 ( + + + + {Tabs.All} + + + {Tabs.Images} + + + {Tabs.Videos} + + + + ); +} + 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 {id}; + const { threadInfo } = props.route.params; + const { id } = threadInfo; + const styles = useStyles(unboundStyles); + + const [activeTab, setActiveTab] = React.useState(Tabs.All); + const flatListContainerRef = React.useRef>(); + const [verticalBounds, setVerticalBounds] = + React.useState(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 ( + + + + + ); } -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 = + 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 }) => ( { + // 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 ( );