Changeset View
Changeset View
Standalone View
Standalone View
native/chat/settings/thread-settings-media-gallery.react.js
// @flow | // @flow | ||||
import { useNavigation, useRoute } from '@react-navigation/native'; | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { View, useWindowDimensions } from 'react-native'; | import { View, useWindowDimensions } from 'react-native'; | ||||
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet'; | |||||
import { FlatList } from 'react-native-gesture-handler'; | import { FlatList } from 'react-native-gesture-handler'; | ||||
import { fetchThreadMedia } from 'lib/actions/thread-actions.js'; | import { fetchThreadMedia } from 'lib/actions/thread-actions.js'; | ||||
import type { MediaInfo } from 'lib/types/media-types'; | import type { MediaInfo, Media } from 'lib/types/media-types'; | ||||
import { useServerCall } from 'lib/utils/action-utils.js'; | import { useServerCall } from 'lib/utils/action-utils.js'; | ||||
import GestureTouchableOpacity from '../../components/gesture-touchable-opacity.react.js'; | import GestureTouchableOpacity from '../../components/gesture-touchable-opacity.react.js'; | ||||
import Multimedia from '../../media/multimedia.react.js'; | import Multimedia from '../../media/multimedia.react.js'; | ||||
import { | |||||
ImageModalRouteName, | |||||
VideoPlaybackModalRouteName, | |||||
} from '../../navigation/route-names.js'; | |||||
import { useStyles } from '../../themes/colors.js'; | import { useStyles } from '../../themes/colors.js'; | ||||
import type { | |||||
LayoutCoordinates, | |||||
VerticalBounds, | |||||
} from '../../types/layout-types.js'; | |||||
const galleryItemGap = 8; | const galleryItemGap = 8; | ||||
const numColumns = 3; | const numColumns = 3; | ||||
type ThreadSettingsMediaGalleryProps = { | type ThreadSettingsMediaGalleryProps = { | ||||
+threadID: string, | +threadID: string, | ||||
+limit: number, | +limit: number, | ||||
+verticalBounds: ?VerticalBounds, | |||||
}; | }; | ||||
function ThreadSettingsMediaGallery( | function ThreadSettingsMediaGallery( | ||||
props: ThreadSettingsMediaGalleryProps, | props: ThreadSettingsMediaGalleryProps, | ||||
): React.Node { | ): React.Node { | ||||
const styles = useStyles(unboundStyles); | const styles = useStyles(unboundStyles); | ||||
const { width } = useWindowDimensions(); | const { width } = useWindowDimensions(); | ||||
// Explanation of galleryItemWidth: | // Explanation of galleryItemWidth: | ||||
// The FlatList has a horizontal padding of 16px on each side, | // The FlatList has a horizontal padding of 16px on each side, | ||||
// and so the width of the actual FlatList is `width - 32px`. | // and so the width of the actual FlatList is `width - 32px`. | ||||
// With three columns, there will be two gaps in between the items, | // With three columns, there will be two gaps in between the items, | ||||
// so the width of each item (with the gaps) will be | // so the width of each item (with the gaps) will be | ||||
// (width - 32px - (numColumns-1) * galleryItemGap) / numColumns. | // (width - 32px - (numColumns-1) * galleryItemGap) / numColumns. | ||||
// E.g. 16px, media, galleryItemGap, media, galleryItemGap, media, 16px | // E.g. 16px, media, galleryItemGap, media, galleryItemGap, media, 16px | ||||
const galleryItemWidth = | const galleryItemWidth = | ||||
(width - 32 - (numColumns - 1) * galleryItemGap) / numColumns; | (width - 32 - (numColumns - 1) * galleryItemGap) / numColumns; | ||||
const { threadID, limit } = props; | const { threadID, limit, verticalBounds } = props; | ||||
const [mediaInfos, setMediaInfos] = React.useState([]); | const [mediaInfos, setMediaInfos] = React.useState([]); | ||||
const callFetchThreadMedia = useServerCall(fetchThreadMedia); | const callFetchThreadMedia = useServerCall(fetchThreadMedia); | ||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const fetchData = async () => { | const fetchData = async () => { | ||||
const result = await callFetchThreadMedia({ | const result = await callFetchThreadMedia({ | ||||
threadID, | threadID, | ||||
limit, | limit, | ||||
Show All 20 Lines | return { | ||||
media: { | media: { | ||||
width: galleryItemWidth, | width: galleryItemWidth, | ||||
...styles.media, | ...styles.media, | ||||
}, | }, | ||||
}; | }; | ||||
}, [galleryItemWidth, styles.media, styles.mediaContainer]); | }, [galleryItemWidth, styles.media, styles.mediaContainer]); | ||||
const renderItem = React.useCallback( | const renderItem = React.useCallback( | ||||
({ item, index }) => { | ({ item, index }) => ( | ||||
const containerStyle = | <MediaGalleryItem | ||||
index % numColumns === 0 | item={item} | ||||
? memoizedStyles.mediaContainer | index={index} | ||||
: memoizedStyles.mediaContainerWithMargin; | memoizedStyles={memoizedStyles} | ||||
threadID={threadID} | |||||
const mediaInfoItem: MediaInfo = { | verticalBounds={verticalBounds} | ||||
...item, | /> | ||||
index, | ), | ||||
}; | [threadID, verticalBounds, memoizedStyles], | ||||
return ( | |||||
<View key={item.id} style={containerStyle}> | |||||
<GestureTouchableOpacity style={memoizedStyles.media}> | |||||
<Multimedia mediaInfo={mediaInfoItem} spinnerColor="black" /> | |||||
</GestureTouchableOpacity> | |||||
</View> | |||||
); | |||||
}, | |||||
[ | |||||
memoizedStyles.media, | |||||
memoizedStyles.mediaContainer, | |||||
memoizedStyles.mediaContainerWithMargin, | |||||
], | |||||
); | ); | ||||
return ( | return ( | ||||
<View style={styles.flatListContainer}> | <View style={styles.flatListContainer}> | ||||
<FlatList | <FlatList | ||||
data={mediaInfos} | data={mediaInfos} | ||||
numColumns={numColumns} | numColumns={numColumns} | ||||
renderItem={renderItem} | renderItem={renderItem} | ||||
/> | /> | ||||
</View> | </View> | ||||
); | ); | ||||
} | } | ||||
type MediaGalleryItemProps = { | |||||
+item: Media, | |||||
+index: number, | |||||
+memoizedStyles: { | |||||
+mediaContainer: ViewStyleProp, | |||||
+mediaContainerWithMargin: ViewStyleProp, | |||||
+media: ViewStyleProp, | |||||
}, | |||||
+threadID: string, | |||||
+verticalBounds: ?VerticalBounds, | |||||
}; | |||||
function MediaGalleryItem(props: MediaGalleryItemProps): React.Node { | |||||
const navigation = useNavigation(); | |||||
const route = useRoute(); | |||||
const ref = React.useRef(null); | |||||
const onLayout = React.useCallback(() => {}, []); | |||||
const { threadID, verticalBounds, memoizedStyles, item, index } = props; | |||||
const mediaInfo: MediaInfo = React.useMemo( | |||||
() => ({ | |||||
...(item: Media), | |||||
index, | |||||
}), | |||||
[item, index], | |||||
); | |||||
const navigateToMedia = React.useCallback(() => { | |||||
ref.current?.measure((x, y, width, height, pageX, pageY) => { | |||||
const initialCoordinates: LayoutCoordinates = { | |||||
x: pageX, | |||||
y: pageY, | |||||
width, | |||||
height, | |||||
}; | |||||
navigation.navigate<'VideoPlaybackModal' | 'ImageModal'>({ | |||||
name: | |||||
mediaInfo.type === 'video' | |||||
? VideoPlaybackModalRouteName | |||||
: ImageModalRouteName, | |||||
key: `multimedia|${threadID}|${mediaInfo.id}`, | |||||
params: { | |||||
presentedFrom: route.key, | |||||
mediaInfo, | |||||
item, | |||||
initialCoordinates, | |||||
verticalBounds, | |||||
}, | |||||
}); | |||||
}); | |||||
}, [navigation, route, threadID, mediaInfo, item, verticalBounds]); | |||||
const containerStyle = | |||||
index % numColumns === 0 | |||||
? memoizedStyles.mediaContainer | |||||
: memoizedStyles.mediaContainerWithMargin; | |||||
return ( | |||||
<View key={item.id} style={containerStyle} onLayout={onLayout} ref={ref}> | |||||
<GestureTouchableOpacity | |||||
onPress={navigateToMedia} | |||||
style={memoizedStyles.media} | |||||
> | |||||
<Multimedia mediaInfo={mediaInfo} spinnerColor="black" /> | |||||
</GestureTouchableOpacity> | |||||
</View> | |||||
); | |||||
} | |||||
const unboundStyles = { | const unboundStyles = { | ||||
flatListContainer: { | flatListContainer: { | ||||
paddingHorizontal: 16, | paddingHorizontal: 16, | ||||
}, | }, | ||||
mediaContainer: { | mediaContainer: { | ||||
height: 180, | height: 180, | ||||
justifyContent: 'center', | justifyContent: 'center', | ||||
alignItems: 'center', | alignItems: 'center', | ||||
}, | }, | ||||
media: { | media: { | ||||
height: 180, | height: 180, | ||||
}, | }, | ||||
}; | }; | ||||
export default ThreadSettingsMediaGallery; | export default ThreadSettingsMediaGallery; |