diff --git a/package-lock.json b/package-lock.json index 551a997..16c2f69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@bankify/redux-persist-realm": "^0.1.3", "@likashefqet/react-native-image-zoom": "^1.3.0", "@react-native-clipboard/clipboard": "^1.11.2", + "@react-native-community/hooks": "^3.0.0", "@react-navigation/bottom-tabs": "^6.5.8", "@react-navigation/native": "^6.1.7", "@react-navigation/native-stack": "^6.9.13", @@ -4112,6 +4113,15 @@ "node": ">= 4.0.0" } }, + "node_modules/@react-native-community/hooks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/hooks/-/hooks-3.0.0.tgz", + "integrity": "sha512-g2OyxXHfwIytXUJitBR6Z/ISoOfp0WKx5FOv+NqJ/CrWjRDcTw6zXE5I1C9axfuh30kJqzWchVfCDrkzZYTxqg==", + "peerDependencies": { + "react": ">=17.0.2", + "react-native": ">=0.65" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.72.0", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz", @@ -18759,6 +18769,12 @@ "joi": "^17.2.1" } }, + "@react-native-community/hooks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/hooks/-/hooks-3.0.0.tgz", + "integrity": "sha512-g2OyxXHfwIytXUJitBR6Z/ISoOfp0WKx5FOv+NqJ/CrWjRDcTw6zXE5I1C9axfuh30kJqzWchVfCDrkzZYTxqg==", + "requires": {} + }, "@react-native/assets-registry": { "version": "0.72.0", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz", diff --git a/package.json b/package.json index a9f5bb6..a2aebef 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@bankify/redux-persist-realm": "^0.1.3", "@likashefqet/react-native-image-zoom": "^1.3.0", "@react-native-clipboard/clipboard": "^1.11.2", + "@react-native-community/hooks": "^3.0.0", "@react-navigation/bottom-tabs": "^6.5.8", "@react-navigation/native": "^6.1.7", "@react-navigation/native-stack": "^6.9.13", diff --git a/src/app.tsx b/src/app.tsx index 787e8c2..f44907a 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -14,7 +14,6 @@ import { store, persistor, validateSettings } from './state'; import { LoadingView } from './components'; import { Welcome } from './screens'; import styles from './styles'; -import { DimensionsProvider } from './contexts'; const App = () => { const [showWelcome, setShowWelcome] = useState(false); @@ -44,30 +43,28 @@ const App = () => { return ( - - - } - persistor={persistor} - onBeforeLift={onBeforeLift}> - - - - - {showWelcome ? ( - setShowWelcome(false)} /> - ) : ( - - )} - - - - - - + + } + persistor={persistor} + onBeforeLift={onBeforeLift}> + + + + + {showWelcome ? ( + setShowWelcome(false)} /> + ) : ( + + )} + + + + + ); }; diff --git a/src/components/floatingActionButton.tsx b/src/components/floatingActionButton.tsx index d02439a..8f2deed 100644 --- a/src/components/floatingActionButton.tsx +++ b/src/components/floatingActionButton.tsx @@ -4,9 +4,9 @@ import { FAB } from 'react-native-paper'; import { ParamListBase, useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { pickSingle } from 'react-native-document-picker'; -import { ORIENTATION, useDimensions } from '../contexts'; import { ROUTE } from '../types'; import { allowedMimeTypes, noOp } from '../utilities'; +import { useDeviceOrientation } from '@react-native-community/hooks'; const floatingActionButtonStyles = StyleSheet.create({ fab: { @@ -22,7 +22,7 @@ const floatingActionButtonStyles = StyleSheet.create({ const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => { const { navigate } = useNavigation>(); - const { orientation } = useDimensions(); + const orientation = useDeviceOrientation(); const [state, setState] = useState(false); const [keyboardOpen, setKeyboardOpen] = useState(false); @@ -70,7 +70,7 @@ const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => { navigate(ROUTE.ADD_MEME, { file }); }} style={ - orientation === ORIENTATION.PORTRAIT + orientation === 'portrait' ? floatingActionButtonStyles.fab : floatingActionButtonStyles.fabLandscape } diff --git a/src/components/hideableHeader.tsx b/src/components/hideableHeader.tsx index b464fd7..2bc0e1a 100644 --- a/src/components/hideableHeader.tsx +++ b/src/components/hideableHeader.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useRef } from 'react'; import { Animated, StyleSheet } from 'react-native'; -import { ORIENTATION, useDimensions } from '../contexts'; -import styles from '../styles'; import { useTheme } from 'react-native-paper'; +import { useDeviceOrientation } from '@react-native-community/hooks'; +import styles from '../styles'; const hideableHeaderStyles = StyleSheet.create({ headerView: { @@ -22,7 +22,7 @@ const HideableHeader = ({ children: React.ReactNode; }) => { const { colors } = useTheme(); - const { orientation } = useDimensions(); + const orientation = useDeviceOrientation(); const headerAnim = useRef(new Animated.Value(visible ? 1 : 0)).current; @@ -38,9 +38,7 @@ const HideableHeader = ({ { return ( ; setMemeTags: (tags: Map) => void; }) => { - const { dimensions } = useDimensions(); + const { width } = useSafeAreaFrame(); - const [imageWidth, setImageWidth] = useState(); - const [imageHeight, setImageHeight] = useState(); + const { dimensions, loading, error } = useImageDimensions({ uri: memeUri }); - useEffect(() => { - // eslint-disable-next-line unicorn/no-useless-undefined - setImageWidth(undefined); - // eslint-disable-next-line unicorn/no-useless-undefined - setImageHeight(undefined); - - Image.getSize(memeUri, (width, height) => { - const paddedWidth = dimensions.width * 0.92; - const paddedHeight = Math.max( - Math.min((paddedWidth / width) * height, 500), - 100, - ); - - setImageWidth(paddedWidth); - setImageHeight(paddedHeight); - }); - }, [memeUri, dimensions.width]); - - if (!imageWidth || !imageHeight) return ; + if (loading || error || !dimensions) return ; return ( <> @@ -75,8 +57,14 @@ const MemeEditor = ({ source={{ uri: memeUri }} style={[ { - width: imageWidth, - height: imageHeight, + width: width * 0.92, + height: Math.max( + Math.min( + ((width * 0.92) / dimensions.width) * dimensions.height, + 500, + ), + 100, + ), }, memeEditorStyles.image, ]} diff --git a/src/components/memes/memeTagSearchModal.tsx b/src/components/memes/memeTagSearchModal.tsx index acf14cb..9a73b22 100644 --- a/src/components/memes/memeTagSearchModal.tsx +++ b/src/components/memes/memeTagSearchModal.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useRef, useState } from 'react'; -import { TagChip } from '../tags'; -import { Tag } from '../../database'; import { useQuery, useRealm } from '@realm/react'; -import { TAG_SORT, tagSortQuery } from '../../types'; import { Chip, Modal, Portal, Searchbar, useTheme } from 'react-native-paper'; import { StyleSheet } from 'react-native'; -import styles from '../../styles'; import { FlashList } from '@shopify/flash-list'; +import { useSafeAreaFrame } from 'react-native-safe-area-context'; +import { TAG_SORT, tagSortQuery } from '../../types'; +import { TagChip } from '../tags'; +import { Tag } from '../../database'; +import styles from '../../styles'; import { validateTagName } from '../../utilities'; const memeTagSearchModalStyles = StyleSheet.create({ @@ -36,6 +37,7 @@ const MemeTagSearchModal = ({ memeTags: Map; setMemeTags: (tags: Map) => void; }) => { + const { width } = useSafeAreaFrame(); const { colors } = useTheme(); const realm = useRealm(); @@ -115,6 +117,10 @@ const MemeTagSearchModal = ({ extraData={memeTags} horizontal estimatedItemSize={120} + estimatedListSize={{ + width: width - 10, + height: 34.5, + }} showsHorizontalScrollIndicator={false} keyboardShouldPersistTaps={'always'} renderItem={({ item: tag }) => ( @@ -135,6 +141,7 @@ const MemeTagSearchModal = ({ Create Tag #{tagName.valid ? tagName.parsed : 'newTag'} )} + fadingEdgeLength={50} /> diff --git a/src/components/memes/memeTagSelector.tsx b/src/components/memes/memeTagSelector.tsx index 6bd04af..a4a00ac 100644 --- a/src/components/memes/memeTagSelector.tsx +++ b/src/components/memes/memeTagSelector.tsx @@ -1,11 +1,11 @@ import React, { ComponentProps, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { Chip } from 'react-native-paper'; +import { FlashList } from '@shopify/flash-list'; +import { useSafeAreaFrame } from 'react-native-safe-area-context'; import { TagChip } from '../tags'; import { Tag } from '../../database'; -import { useDimensions } from '../../contexts'; import { MemeTagSearchModal } from '.'; -import { FlashList } from '@shopify/flash-list'; const memeTagSelectorStyles = StyleSheet.create({ tagChip: { @@ -21,7 +21,7 @@ const MemeTagSelector = ({ memeTags: Map; setMemeTags: (tags: Map) => void; } & ComponentProps) => { - const { dimensions } = useDimensions(); + const { width } = useSafeAreaFrame(); const [flashListMargin, setFlashListMargin] = useState(0); const [tagSearchModalVisible, setTagSearchModalVisible] = useState(false); @@ -37,7 +37,6 @@ const MemeTagSelector = ({ setTagSearchModalVisible(true)} onLayout={event => setFlashListMargin( - dimensions.width * 0.92 - event.nativeEvent.layout.width, + width * 0.92 - event.nativeEvent.layout.width, ) } style={{ @@ -64,6 +63,7 @@ const MemeTagSelector = ({ Add Tag )} + fadingEdgeLength={50} /> { - const { dimensions } = useDimensions(); - const { colors } = useTheme(); + const { height, width } = useSafeAreaFrame(); - const [imageWidth, setImageWidth] = useState(); - const [imageHeight, setImageHeight] = useState(); + const { dimensions, loading, error } = useImageDimensions({ uri: meme.uri }); - useEffect(() => { - Image.getSize(meme.uri, (width, height) => { - const ratio = width / height; - const screenRatio = dimensions.width / dimensions.height - 160; - - if (ratio > screenRatio) { - setImageWidth(dimensions.width); - setImageHeight(dimensions.width / ratio); - } else { - setImageWidth(dimensions.height * ratio); - setImageHeight(dimensions.height); - } - }); - }, [meme.uri, dimensions.width, dimensions.height]); + if (loading || error || !dimensions) return ; return ( - - {imageWidth && imageHeight ? ( - - ) : ( - - )} + + width / (height - 128) + ? { + width, + height: width / (dimensions.width / dimensions.height), + } + : { + width: (height - 128) * (dimensions.width / dimensions.height), + height: height - 128, + } + } + minScale={0.5} + /> ); }; diff --git a/src/components/memes/memesList/memesGridItem.tsx b/src/components/memes/memesList/memesGridItem.tsx index d211e13..d6acb22 100644 --- a/src/components/memes/memesList/memesGridItem.tsx +++ b/src/components/memes/memesList/memesGridItem.tsx @@ -1,8 +1,9 @@ -import React, { useState } from 'react'; -import { Image, TouchableHighlight, View } from 'react-native'; +import React from 'react'; +import { Image, TouchableHighlight } from 'react-native'; import { useSelector } from 'react-redux'; +import { useSafeAreaFrame } from 'react-native-safe-area-context'; +import { useImageDimensions } from '@react-native-community/hooks'; import { Meme } from '../../../database'; -import { useDimensions } from '../../../contexts'; import { RootState } from '../../../state'; const MemesGridItem = ({ @@ -14,33 +15,27 @@ const MemesGridItem = ({ index: number; focusMeme: (index: number) => void; }) => { - const { dimensions } = useDimensions(); + const { width } = useSafeAreaFrame(); const gridColumns = useSelector( (state: RootState) => state.settings.gridColumns, ); - const [imageWidth, setImageWidth] = useState(); - const [imageHeight, setImageHeight] = useState(); + const { dimensions, loading, error } = useImageDimensions({ uri: meme.uri }); - Image.getSize(meme.uri, () => { - const paddedWidth = (dimensions.width * 0.92 - 5) / gridColumns; - setImageWidth(paddedWidth); - setImageHeight(paddedWidth); - }); + if (loading || error || !dimensions) return <>; return ( - <> - {imageWidth && imageHeight && ( - - focusMeme(index)}> - - - - )} - + focusMeme(index)}> + + ); }; diff --git a/src/components/memes/memesList/memesList.tsx b/src/components/memes/memesList/memesList.tsx index 595ecb3..d1b6766 100644 --- a/src/components/memes/memesList/memesList.tsx +++ b/src/components/memes/memesList/memesList.tsx @@ -7,7 +7,7 @@ import { } from 'react-native'; import { useSelector } from 'react-redux'; import { Divider, HelperText } from 'react-native-paper'; -import { useDimensions, ORIENTATION } from '../../../contexts'; +import { useSafeAreaFrame } from 'react-native-safe-area-context'; import { Meme } from '../../../database'; import { RootState } from '../../../state'; import { VIEW } from '../../../types'; @@ -17,35 +17,32 @@ import MemesMasonryItem from './memesMasonryItem'; import MemesGridItem from './memesGridItem'; import MemesListItem from './memesListItem'; -const memesMasonryListStyles = StyleSheet.create({ +const sharedMemesListStyles = StyleSheet.create({ flashList: { paddingBottom: 100, - // Needed to prevent fucky MasonryFlashList, see https://github.com/Shopify/flash-list/issues/876 - paddingHorizontal: 0.1, }, helperText: { marginVertical: 15, }, }); +const memesMasonryListStyles = StyleSheet.create({ + flashList: { + // Needed to prevent fucky MasonryFlashList, see https://github.com/Shopify/flash-list/issues/876 + paddingHorizontal: 0.1, + }, +}); + const memesGridListStyles = StyleSheet.create({ flashList: { - paddingBottom: 100, paddingHorizontal: 2.5, }, - helperText: { - marginVertical: 12.5, - }, }); const memesListListStyles = StyleSheet.create({ flashList: { - paddingBottom: 100, paddingHorizontal: 5, }, - helperText: { - marginVertical: 15, - }, }); const MemesList = ({ @@ -61,7 +58,7 @@ const MemesList = ({ handleScroll: (event: NativeSyntheticEvent) => void; focusMeme: (index: number) => void; }) => { - const { dimensions, orientation } = useDimensions(); + const { height, width } = useSafeAreaFrame(); const view = useSelector((state: RootState) => state.memes.view); const masonryColumns = useSelector( (state: RootState) => state.settings.masonryColumns, @@ -70,10 +67,6 @@ const MemesList = ({ (state: RootState) => state.settings.gridColumns, ); - const extraFlashListPadding = - flashListPadding + - dimensions.height * (orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04); - return ( <> {view === VIEW.MASONRY && ( @@ -82,8 +75,8 @@ const MemesList = ({ data={memes} estimatedItemSize={getFlashListItemHeight(masonryColumns)} estimatedListSize={{ - height: dimensions.height, - width: dimensions.width * 0.92, + height, + width: width * 0.92, }} numColumns={masonryColumns} showsVerticalScrollIndicator={false} @@ -91,17 +84,19 @@ const MemesList = ({ )} contentContainerStyle={{ - paddingTop: extraFlashListPadding, + paddingTop: flashListPadding, + ...sharedMemesListStyles.flashList, ...memesMasonryListStyles.flashList, }} ListEmptyComponent={() => ( + style={[sharedMemesListStyles.helperText, styles.centerText]}> No memes found )} onScroll={handleScroll} + fadingEdgeLength={100} /> )} {view === VIEW.GRID && ( @@ -110,8 +105,8 @@ const MemesList = ({ data={memes} estimatedItemSize={getFlashListItemHeight(gridColumns)} estimatedListSize={{ - height: dimensions.height, - width: dimensions.width * 0.92, + height: height, + width: width * 0.92, }} numColumns={gridColumns} showsVerticalScrollIndicator={false} @@ -119,17 +114,19 @@ const MemesList = ({ )} contentContainerStyle={{ - paddingTop: extraFlashListPadding + 2.5, + paddingTop: flashListPadding, + ...sharedMemesListStyles.flashList, ...memesGridListStyles.flashList, }} ListEmptyComponent={() => ( + style={[sharedMemesListStyles.helperText, styles.centerText]}> No memes found )} onScroll={handleScroll} + fadingEdgeLength={100} /> )} {view === VIEW.LIST && ( @@ -138,8 +135,8 @@ const MemesList = ({ data={memes} estimatedItemSize={50} estimatedListSize={{ - height: dimensions.height, - width: dimensions.width * 0.92, + height: height, + width: width * 0.92, }} showsVerticalScrollIndicator={false} renderItem={({ item: meme, index }) => ( @@ -147,17 +144,19 @@ const MemesList = ({ )} ItemSeparatorComponent={() => } contentContainerStyle={{ - paddingTop: extraFlashListPadding, + paddingTop: flashListPadding, + ...sharedMemesListStyles.flashList, ...memesListListStyles.flashList, }} ListEmptyComponent={() => ( + style={[sharedMemesListStyles.helperText, styles.centerText]}> No memes found )} onScroll={handleScroll} + fadingEdgeLength={100} /> )} diff --git a/src/components/memes/memesList/memesListItem.tsx b/src/components/memes/memesList/memesListItem.tsx index 9f70d4c..afe38c6 100644 --- a/src/components/memes/memesList/memesListItem.tsx +++ b/src/components/memes/memesList/memesListItem.tsx @@ -1,15 +1,18 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Image, StyleSheet, View } from 'react-native'; import { Text, TouchableRipple } from 'react-native-paper'; +import { useSafeAreaFrame } from 'react-native-safe-area-context'; +import { useImageDimensions } from '@react-native-community/hooks'; import { Meme } from '../../../database'; import styles from '../../../styles'; -import { useDimensions } from '../../../contexts'; const memesListItemStyles = StyleSheet.create({ view: { paddingVertical: 10, }, image: { + height: 75, + width: 75, borderRadius: 5, }, detailsView: { @@ -30,71 +33,54 @@ const MemesListItem = ({ index: number; focusMeme: (index: number) => void; }) => { - const { dimensions } = useDimensions(); + const { width } = useSafeAreaFrame(); - const [imageWidth, setImageWidth] = useState(); - const [imageHeight, setImageHeight] = useState(); + const { dimensions, loading, error } = useImageDimensions({ uri: meme.uri }); - Image.getSize(meme.uri, () => { - const paddedWidth = 75; - setImageWidth(paddedWidth); - setImageHeight(paddedWidth); - }); + if (loading || error || !dimensions) return <>; return ( - <> - {imageWidth && imageHeight && ( - focusMeme(index)} - style={[memesListItemStyles.view, styles.flexRow]}> - <> - - focusMeme(index)} + style={[memesListItemStyles.view, styles.flexRow]}> + <> + + + + {meme.title} + + + + {meme.dateModified.toLocaleDateString()} • {meme.size / 1000} + KB + + + + {meme.tags.map(tag => ( + - - - - {meme.title} + numberOfLines={1}> + #{tag.name} - - - {meme.dateModified.toLocaleDateString()} • {meme.size / 1000} - KB - - - - {meme.tags.map(tag => ( - - #{tag.name} - - ))} - - - - - )} - + ))} + + + + ); }; diff --git a/src/components/memes/memesList/memesMasonryItem.tsx b/src/components/memes/memesList/memesMasonryItem.tsx index 14657b0..739a49d 100644 --- a/src/components/memes/memesList/memesMasonryItem.tsx +++ b/src/components/memes/memesList/memesMasonryItem.tsx @@ -1,9 +1,10 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Image, StyleSheet, TouchableHighlight } from 'react-native'; import { useSelector } from 'react-redux'; +import { useSafeAreaFrame } from 'react-native-safe-area-context'; import { Meme } from '../../../database'; -import { useDimensions } from '../../../contexts'; import { RootState } from '../../../state'; +import { useImageDimensions } from '@react-native-community/hooks'; const memeMasonryItemStyles = StyleSheet.create({ view: { @@ -24,36 +25,31 @@ const MemesMasonryItem = ({ index: number; focusMeme: (index: number) => void; }) => { - const { dimensions } = useDimensions(); + const { width } = useSafeAreaFrame(); const masonryColumns = useSelector( (state: RootState) => state.settings.masonryColumns, ); - const [imageWidth, setImageWidth] = useState(); - const [imageHeight, setImageHeight] = useState(); + const { dimensions, loading, error } = useImageDimensions({ uri: meme.uri }); - Image.getSize(meme.uri, (width, height) => { - const paddedWidth = (dimensions.width * 0.92) / masonryColumns - 5; - setImageWidth(paddedWidth); - setImageHeight((paddedWidth / width) * height); - }); + if (loading || error || !dimensions) return <>; return ( - <> - {imageWidth && imageHeight && ( - focusMeme(index)} - style={memeMasonryItemStyles.view}> - - - )} - + focusMeme(index)} + style={memeMasonryItemStyles.view}> + + ); }; diff --git a/src/components/tags/tagChip.tsx b/src/components/tags/tagChip.tsx index 2b4ebdb..7095ba9 100644 --- a/src/components/tags/tagChip.tsx +++ b/src/components/tags/tagChip.tsx @@ -14,11 +14,13 @@ const tagChipStyles = StyleSheet.create({ const TagChip = ({ tag, active = true, + elevated = false, onPress, ...props }: { tag: Tag; active?: boolean; + elevated?: boolean; onPress?: () => void; } & Omit, 'children'>) => { const theme = useTheme(); @@ -45,6 +47,7 @@ const TagChip = ({ ); }} compact + elevated={elevated} theme={chipTheme} mode={active ? 'flat' : 'outlined'} style={[tagChipStyles.chip, props.style]} diff --git a/src/components/tags/tagPreview.tsx b/src/components/tags/tagPreview.tsx index 9b2ecd4..c5cacc0 100644 --- a/src/components/tags/tagPreview.tsx +++ b/src/components/tags/tagPreview.tsx @@ -3,10 +3,12 @@ import { StyleSheet, View } from 'react-native'; import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'; import { Chip, useTheme } from 'react-native-paper'; import styles from '../../styles'; -import { useDimensions } from '../../contexts'; import { getContrastColor } from '../../utilities'; const tagPreviewStyles = StyleSheet.create({ + view: { + margin: '10%', + }, chip: { padding: 5, }, @@ -17,7 +19,6 @@ const tagPreviewStyles = StyleSheet.create({ const TagPreview = ({ name, color }: { name: string; color: string }) => { const theme = useTheme(); - const { responsive } = useDimensions(); const chipTheme = useMemo(() => { return { @@ -32,14 +33,7 @@ const TagPreview = ({ name, color }: { name: string; color: string }) => { const contrastColor = getContrastColor(color); return ( - + { return ; diff --git a/src/contexts/dimensions.tsx b/src/contexts/dimensions.tsx deleted file mode 100644 index 7a29295..0000000 --- a/src/contexts/dimensions.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { - ReactNode, - createContext, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; -import { Dimensions, ScaledSize } from 'react-native'; - -const guidelineBaseWidth = 350; -const guidelineBaseHeight = 680; - -enum ORIENTATION { - PORTRAIT = 'portrait', - LANDSCAPE = 'landscape', -} - -interface ScaleFunctions { - horizontalScale: (size: number) => number; - verticalScale: (size: number) => number; - moderateHorizontalScale: (size: number, factor?: number) => number; - moderateVerticalScale: (size: number, factor?: number) => number; -} - -interface DimensionsContext { - orientation: ORIENTATION; - dimensions: ScaledSize; - responsive: ScaleFunctions; -} - -const createScaleFunctions = (dimensionsIn: ScaledSize) => { - const horizontalScale = (size: number) => - (dimensionsIn.width / guidelineBaseWidth) * size; - const verticalScale = (size: number) => - (dimensionsIn.height / guidelineBaseHeight) * size; - const moderateHorizontalScale = (size: number, factor = 0.5) => - size + (horizontalScale(size) - size) * factor; - const moderateVerticalScale = (size: number, factor = 0.5) => - size + (verticalScale(size) - size) * factor; - - return { - horizontalScale, - verticalScale, - moderateHorizontalScale, - moderateVerticalScale, - }; -}; - -const DimensionsContext = createContext( - undefined, -); - -const DimensionsProvider = ({ children }: { children: ReactNode }) => { - const [dimensions, setDimensions] = useState(Dimensions.get('window')); - - const orientation = useMemo(() => { - return dimensions.width > dimensions.height - ? ORIENTATION.LANDSCAPE - : ORIENTATION.PORTRAIT; - }, [dimensions]); - - const responsiveScale = createScaleFunctions(dimensions); - - useEffect(() => { - const onChange = ({ window }: { window: ScaledSize }) => { - setDimensions(window); - }; - - const subscription = Dimensions.addEventListener('change', onChange); - - return () => { - subscription.remove(); - }; - }, []); - - return ( - - {children} - - ); -}; - -const useDimensions = (): DimensionsContext => { - const context = useContext(DimensionsContext); - if (!context) { - throw new Error('useDimensions must be used within a DimensionsProvider'); - } - return context; -}; - -export { ORIENTATION, DimensionsProvider, useDimensions }; diff --git a/src/contexts/index.ts b/src/contexts/index.ts deleted file mode 100644 index cb4d26e..0000000 --- a/src/contexts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ORIENTATION, DimensionsProvider, useDimensions } from './dimensions'; diff --git a/src/screens/addMeme.tsx b/src/screens/addMeme.tsx index be7c3b5..92d3abe 100644 --- a/src/screens/addMeme.tsx +++ b/src/screens/addMeme.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useRef, useState } from 'react'; import { Appbar, Button, useTheme } from 'react-native-paper'; import { useNavigation } from '@react-navigation/native'; -import { ORIENTATION, useDimensions } from '../contexts'; import { ScrollView, StyleSheet, View } from 'react-native'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { useRealm } from '@realm/react'; @@ -9,16 +8,17 @@ import { BSON } from 'realm'; import { AndroidScoped, FileSystem } from 'react-native-file-access'; import { useSelector } from 'react-redux'; import { extension } from 'react-native-mime-types'; +import { useDeviceOrientation } from '@react-native-community/hooks'; +import { + DocumentPickerResponse, + pickSingle, +} from 'react-native-document-picker'; import styles from '../styles'; import { ROUTE, RootStackParamList } from '../types'; import { Meme, Tag } from '../database'; import { RootState } from '../state'; import { allowedMimeTypes, getMemeType, validateMemeTitle } from '../utilities'; import { MemeEditor } from '../components'; -import { - DocumentPickerResponse, - pickSingle, -} from 'react-native-document-picker'; const addMemeStyles = StyleSheet.create({ saveAndAddButton: { @@ -36,7 +36,7 @@ const AddMeme = ({ }: NativeStackScreenProps) => { const { goBack } = useNavigation(); const { colors } = useTheme(); - const { orientation } = useDimensions(); + const orientation = useDeviceOrientation(); const realm = useRealm(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const storageUri = useSelector( @@ -104,7 +104,7 @@ const AddMeme = ({ { const { goBack } = useNavigation(); const { colors } = useTheme(); - const { orientation } = useDimensions(); + const orientation = useDeviceOrientation(); const realm = useRealm(); const [tagName, setTagName] = useState(validateTagName('newTag')); @@ -57,7 +57,7 @@ const AddTag = () => { ) => { const { goBack } = useNavigation(); const { colors } = useTheme(); - const { orientation } = useDimensions(); + const orientation = useDeviceOrientation(); const realm = useRealm(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -80,7 +80,7 @@ const EditMeme = ({ ) => { const { goBack } = useNavigation(); const { colors } = useTheme(); - const { orientation } = useDimensions(); + const orientation = useDeviceOrientation(); const realm = useRealm(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -52,7 +52,7 @@ const EditTag = ({ ) => { - const { orientation, dimensions } = useDimensions(); + const { height, width } = useSafeAreaFrame(); const navigation = useNavigation>(); const realm = useRealm(); @@ -55,35 +65,30 @@ const MemeView = ({ return ( <> - + navigation.goBack()} /> - - { - const newIndex = Math.round( - event.nativeEvent.contentOffset.x / - event.nativeEvent.layoutMeasurement.width, - ); - if (newIndex !== index) setIndex(newIndex); - }} - estimatedItemSize={dimensions.width} - pagingEnabled - horizontal - showsHorizontalScrollIndicator={false} - estimatedListSize={{ - height: dimensions.height - 160, - width: dimensions.width, - }} - renderItem={({ item: meme }) => } - /> - - + { + const newIndex = Math.round( + event.nativeEvent.contentOffset.x / + event.nativeEvent.layoutMeasurement.width, + ); + if (newIndex !== index) setIndex(newIndex); + }} + estimatedItemSize={width} + estimatedListSize={{ height, width }} + pagingEnabled + horizontal + showsHorizontalScrollIndicator={false} + renderItem={({ item: meme }) => } + /> + favoriteMeme(realm, memes[index])} diff --git a/src/screens/memes.tsx b/src/screens/memes.tsx index 9312dd0..4ef3f15 100644 --- a/src/screens/memes.tsx +++ b/src/screens/memes.tsx @@ -4,6 +4,7 @@ import { NativeScrollEvent, NativeSyntheticEvent, View, + useWindowDimensions, } from 'react-native'; import { useQuery } from '@realm/react'; import { useTheme } from 'react-native-paper'; @@ -20,11 +21,14 @@ import { ROUTE, SORT_DIRECTION, memesSortQuery } from '../types'; import { RootState, setNavVisible } from '../state'; import { Meme } from '../database'; import { HideableHeader, MemesHeader, MemesList } from '../components'; +import { useDeviceOrientation } from '@react-native-community/hooks'; const Memes = () => { const { colors } = useTheme(); const { navigate } = useNavigation>(); + const { height } = useWindowDimensions(); + const orientation = useDeviceOrientation(); const sort = useSelector((state: RootState) => state.memes.sort); const sortDirection = useSelector( (state: RootState) => state.memes.sortDirection, @@ -135,7 +139,10 @@ const Memes = () => { search={search} setSearch={setSearch} onLayout={event => { - setFlashListPadding(event.nativeEvent.layout.height); + setFlashListPadding( + event.nativeEvent.layout.height + + height * (orientation === 'portrait' ? 0.02 : 0.04), + ); }} /> diff --git a/src/screens/settings.tsx b/src/screens/settings.tsx index c7e9fab..a030d94 100644 --- a/src/screens/settings.tsx +++ b/src/screens/settings.tsx @@ -13,6 +13,7 @@ import { import { openDocumentTree } from 'react-native-scoped-storage'; import { useDispatch, useSelector } from 'react-redux'; import type {} from 'redux-thunk/extend-redux'; +import { useDeviceOrientation } from '@react-native-community/hooks'; import styles from '../styles'; import { RootState, @@ -21,7 +22,6 @@ import { setNoMedia, setStorageUri, } from '../state'; -import { ORIENTATION, useDimensions } from '../contexts'; const settingsStyles = StyleSheet.create({ snackbar: { @@ -34,7 +34,7 @@ const settingsStyles = StyleSheet.create({ const Settings = () => { const { colors } = useTheme(); - const { orientation } = useDimensions(); + const orientation = useDeviceOrientation(); const noMedia = useSelector((state: RootState) => state.settings.noMedia); const masonryColumns = useSelector( (state: RootState) => state.settings.masonryColumns, @@ -60,85 +60,80 @@ const Settings = () => { <> - - - Views - - Masonry Columns - - + Views + + Masonry Columns + + { + void dispatch( + setMasonryColumns(Number.parseInt(value) as 1 | 2 | 3 | 4), + ); + }} + buttons={[ + { label: '1', value: '1' }, + { label: '2', value: '2' }, + { label: '3', value: '3' }, + { label: '4', value: '4' }, + ]} + style={settingsStyles.marginBottom} + /> + + Grid Columns + + { + void dispatch( + setGridColumns(Number.parseInt(value) as 1 | 2 | 3 | 4), + ); + }} + buttons={[ + { label: '1', value: '1' }, + { label: '2', value: '2' }, + { label: '3', value: '3' }, + { label: '4', value: '4' }, + ]} + /> + + + Media Storage + + + Hide media from gallery + { - void dispatch( - setMasonryColumns(Number.parseInt(value) as 1 | 2 | 3 | 4), - ); + void dispatch(setNoMedia(value)); }} - buttons={[ - { label: '1', value: '1' }, - { label: '2', value: '2' }, - { label: '3', value: '3' }, - { label: '4', value: '4' }, - ]} - style={settingsStyles.marginBottom} /> - - Grid Columns - - { - void dispatch( - setGridColumns(Number.parseInt(value) as 1 | 2 | 3 | 4), - ); - }} - buttons={[ - { label: '1', value: '1' }, - { label: '2', value: '2' }, - { label: '3', value: '3' }, - { label: '4', value: '4' }, - ]} - /> - - - Media Storage - - - Hide media from gallery - { - void dispatch(setNoMedia(value)); - }} - /> - - - + + Database